mirror of https://github.com/docker/docs.git
Merge branch 'master' of github.com:kitematic/kitematic
This commit is contained in:
commit
a8a72a16c9
|
|
@ -1,5 +1,8 @@
|
||||||
root: true
|
root: true
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
- react
|
||||||
|
|
||||||
ecmaFeatures:
|
ecmaFeatures:
|
||||||
modules: true
|
modules: true
|
||||||
jsx: true
|
jsx: true
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,8 @@
|
||||||
"babel": "^5.1.10",
|
"babel": "^5.1.10",
|
||||||
"babel-jest": "^5.2.0",
|
"babel-jest": "^5.2.0",
|
||||||
"electron-prebuilt": "^0.27.3",
|
"electron-prebuilt": "^0.27.3",
|
||||||
"eslint": "^1.0.0",
|
"eslint": "^1.3.1",
|
||||||
|
"eslint-plugin-react": "^3.3.0",
|
||||||
"grunt": "^0.4.5",
|
"grunt": "^0.4.5",
|
||||||
"grunt-babel": "^5.0.1",
|
"grunt-babel": "^5.0.1",
|
||||||
"grunt-chmod": "^1.0.3",
|
"grunt-chmod": "^1.0.3",
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,16 @@ var ContainerDetailsSubheader = React.createClass({
|
||||||
containerActions.start(this.props.container.Name);
|
containerActions.start(this.props.container.Name);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
handleDocs: function () {
|
||||||
|
let repoUri = 'https://hub.docker.com/r/';
|
||||||
|
let imageName = this.props.container.Config.Image.split(':')[0];
|
||||||
|
if (imageName.indexOf('/') === -1) {
|
||||||
|
repoUri = repoUri + 'library/' + imageName;
|
||||||
|
} else {
|
||||||
|
repoUri = repoUri + imageName;
|
||||||
|
}
|
||||||
|
shell.openExternal(repoUri);
|
||||||
|
},
|
||||||
handleTerminal: function () {
|
handleTerminal: function () {
|
||||||
if (!this.disableTerminal()) {
|
if (!this.disableTerminal()) {
|
||||||
metrics.track('Terminaled Into Container');
|
metrics.track('Terminaled Into Container');
|
||||||
|
|
@ -119,6 +129,10 @@ var ContainerDetailsSubheader = React.createClass({
|
||||||
action: true,
|
action: true,
|
||||||
disabled: this.disableTerminal()
|
disabled: this.disableTerminal()
|
||||||
});
|
});
|
||||||
|
var docsActionClass = classNames({
|
||||||
|
action: true,
|
||||||
|
disabled: false
|
||||||
|
});
|
||||||
|
|
||||||
var currentRoutes = _.map(this.context.router.getCurrentRoutes(), r => r.name);
|
var currentRoutes = _.map(this.context.router.getCurrentRoutes(), r => r.name);
|
||||||
var currentRoute = _.last(currentRoutes);
|
var currentRoute = _.last(currentRoutes);
|
||||||
|
|
@ -149,6 +163,7 @@ var ContainerDetailsSubheader = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="details-subheader">
|
<div className="details-subheader">
|
||||||
<div className="details-header-actions">
|
<div className="details-header-actions">
|
||||||
|
|
@ -161,6 +176,10 @@ var ContainerDetailsSubheader = React.createClass({
|
||||||
<div className="action-icon" onClick={this.handleTerminal}><span className="icon icon-docker-exec"></span></div>
|
<div className="action-icon" onClick={this.handleTerminal}><span className="icon icon-docker-exec"></span></div>
|
||||||
<div className="btn-label">EXEC</div>
|
<div className="btn-label">EXEC</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className={docsActionClass}>
|
||||||
|
<div className="action-icon" onClick={this.handleDocs}><span className="icon icon-open-external"></span></div>
|
||||||
|
<div className="btn-label">DOCS</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="details-subheader-tabs">
|
<div className="details-subheader-tabs">
|
||||||
<span className={tabHomeClasses} onClick={this.showHome}>Home</span>
|
<span className={tabHomeClasses} onClick={this.showHome}>Home</span>
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ var ContainerHome = React.createClass({
|
||||||
|
|
||||||
render: function () {
|
render: function () {
|
||||||
if (!this.props.container) {
|
if (!this.props.container) {
|
||||||
return;
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
let body;
|
let body;
|
||||||
|
|
@ -70,6 +70,9 @@ var ContainerHome = React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
sum = sum / this.props.container.Progress.amount;
|
sum = sum / this.props.container.Progress.amount;
|
||||||
|
if (isNaN(sum)) {
|
||||||
|
sum = 0;
|
||||||
|
}
|
||||||
|
|
||||||
body = (
|
body = (
|
||||||
<div className="details-progress">
|
<div className="details-progress">
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ import _ from 'underscore';
|
||||||
import React from 'react/addons';
|
import React from 'react/addons';
|
||||||
import shell from 'shell';
|
import shell from 'shell';
|
||||||
import ContainerUtil from '../utils/ContainerUtil';
|
import ContainerUtil from '../utils/ContainerUtil';
|
||||||
|
import containerActions from '../actions/ContainerActions';
|
||||||
|
import containerStore from '../stores/ContainerStore';
|
||||||
import metrics from '../utils/MetricsUtil';
|
import metrics from '../utils/MetricsUtil';
|
||||||
import {webPorts} from '../utils/Util';
|
import {webPorts} from '../utils/Util';
|
||||||
|
|
||||||
|
|
@ -11,50 +13,88 @@ var ContainerSettingsPorts = React.createClass({
|
||||||
},
|
},
|
||||||
getInitialState: function () {
|
getInitialState: function () {
|
||||||
return {
|
return {
|
||||||
ports: {},
|
ports: ContainerUtil.ports(this.props.container)
|
||||||
defaultPort: null
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
componentDidMount: function() {
|
|
||||||
if (!this.props.container) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var ports = ContainerUtil.ports(this.props.container);
|
|
||||||
this.setState({
|
|
||||||
ports: ports,
|
|
||||||
defaultPort: _.find(_.keys(ports), function (port) {
|
|
||||||
return webPorts.indexOf(port) !== -1;
|
|
||||||
})
|
|
||||||
});
|
|
||||||
},
|
|
||||||
handleViewLink: function (url) {
|
handleViewLink: function (url) {
|
||||||
metrics.track('Opened In Browser', {
|
metrics.track('Opened In Browser', {
|
||||||
from: 'settings'
|
from: 'settings'
|
||||||
});
|
});
|
||||||
shell.openExternal(url);
|
shell.openExternal(url);
|
||||||
},
|
},
|
||||||
handleChangeDefaultPort: function (port, e) {
|
handleChangePort: function(key, e) {
|
||||||
if (e.target.checked) {
|
var ports = this.state.ports;
|
||||||
this.setState({
|
var port = e.target.value;
|
||||||
defaultPort: port
|
|
||||||
|
// save updated port
|
||||||
|
ports[key] = _.extend(ports[key], {
|
||||||
|
url: 'http://' + ports[key]['ip'] + ':' + port,
|
||||||
|
port: port,
|
||||||
|
error: null
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
this.setState({
|
// basic validation, if number is integer, if its in range, if there
|
||||||
defaultPort: null
|
// is no collision with ports of other containers and also if there is no
|
||||||
|
// collision with ports for current containser
|
||||||
|
const name = this.props.container.Name;
|
||||||
|
const containers = containerStore.getState().containers;
|
||||||
|
const container = ContainerUtil.isPortCollision(name, containers, port);
|
||||||
|
const duplicates = _.filter(ports, (v, i) => {
|
||||||
|
return (i != key && _.isEqual(v.port, port));
|
||||||
});
|
});
|
||||||
|
if (!port.match(/^[0-9]+$/g)) {
|
||||||
|
ports[key].error = 'Needs to be an integer.';
|
||||||
}
|
}
|
||||||
|
else if (port <= 0 || port > 65535) {
|
||||||
|
ports[key].error = 'Needs to be in range <1,65535>.';
|
||||||
|
}
|
||||||
|
else if (container) {
|
||||||
|
ports[key].error = 'Collision with port at container "'+ container.Name +'"';
|
||||||
|
}
|
||||||
|
else if (duplicates.length > 0) {
|
||||||
|
ports[key].error = "Collision with other port at container.";
|
||||||
|
}
|
||||||
|
else if (port == 22 || port == 2376) {
|
||||||
|
ports[key].error = "Ports 22 and 2376 are reserved ports for Kitematic/Docker.";
|
||||||
|
}
|
||||||
|
this.setState({ ports: ports });
|
||||||
|
},
|
||||||
|
handleSave: function() {
|
||||||
|
containerActions.update(this.props.container.Name, {
|
||||||
|
NetworkSettings: {
|
||||||
|
Ports: _.reduce(this.state.ports, function(res, value, key) {
|
||||||
|
res[key + '/tcp'] = [{
|
||||||
|
HostIp: value.ip,
|
||||||
|
HostPort: value.port,
|
||||||
|
}];
|
||||||
|
return res;
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
render: function () {
|
render: function () {
|
||||||
if (!this.props.container) {
|
if (!this.props.container) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
var isUpdating = (this.props.container.State.Updating);
|
||||||
|
var isValid = true;
|
||||||
var ports = _.map(_.pairs(this.state.ports), pair => {
|
var ports = _.map(_.pairs(this.state.ports), pair => {
|
||||||
var key = pair[0];
|
var key = pair[0];
|
||||||
var val = pair[1];
|
var {ip, port, url, error} = pair[1];
|
||||||
|
isValid = (error) ? false : isValid;
|
||||||
|
let ipLink = (this.props.container.State.Running && !this.props.container.State.Paused && !this.props.container.State.ExitCode && !this.props.container.State.Restarting) ? (<a onClick={this.handleViewLink.bind(this, url)}>{ip}</a>):({ip});
|
||||||
return (
|
return (
|
||||||
<tr key={key}>
|
<tr key={key}>
|
||||||
<td>{key}</td>
|
<td>{key}</td>
|
||||||
<td><a onClick={this.handleViewLink.bind(this, val.url)}>{val.display}</a></td>
|
<td className="bind">
|
||||||
|
{ipLink}:
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
disabled={isUpdating}
|
||||||
|
onChange={this.handleChangePort.bind(this, key)}
|
||||||
|
defaultValue={port} />
|
||||||
|
</td>
|
||||||
|
<td className="error">{error}</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
@ -66,13 +106,19 @@ var ContainerSettingsPorts = React.createClass({
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>DOCKER PORT</th>
|
<th>DOCKER PORT</th>
|
||||||
<th>ACCESS URL</th>
|
<th>MAC IP:PORT</th>
|
||||||
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{ports}
|
{ports}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<a className="btn btn-action"
|
||||||
|
disabled={isUpdating || !isValid}
|
||||||
|
onClick={this.handleSave}>
|
||||||
|
Save
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -28,21 +28,42 @@ var ContainerUtil = {
|
||||||
}
|
}
|
||||||
var res = {};
|
var res = {};
|
||||||
var ip = docker.host;
|
var ip = docker.host;
|
||||||
_.each(container.NetworkSettings.Ports, function (value, key) {
|
var ports = (container.NetworkSettings.Ports) ? container.NetworkSettings.Ports : (container.HostConfig.PortBindings) ? container.HostConfig.PortBindings : container.Config.ExposedPorts;
|
||||||
|
_.each(ports, function (value, key) {
|
||||||
var dockerPort = key.split('/')[0];
|
var dockerPort = key.split('/')[0];
|
||||||
var localUrl = null;
|
var localUrl = null;
|
||||||
var localUrlDisplay = null;
|
var localUrlDisplay = null;
|
||||||
|
var port = null;
|
||||||
if (value && value.length) {
|
if (value && value.length) {
|
||||||
var port = value[0].HostPort;
|
var port = value[0].HostPort;
|
||||||
localUrl = 'http://' + ip + ':' + port;
|
localUrl = 'http://' + ip + ':' + port;
|
||||||
localUrlDisplay = ip + ':' + port;
|
|
||||||
}
|
}
|
||||||
res[dockerPort] = {
|
res[dockerPort] = {
|
||||||
url: localUrl,
|
url: localUrl,
|
||||||
display: localUrlDisplay
|
ip: ip,
|
||||||
|
port: port
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if there is port colision with other containers
|
||||||
|
* @param {String} name name of the current container
|
||||||
|
* @param {Array} containers array of all containers
|
||||||
|
* @param {String} port
|
||||||
|
* @return {Object|null} return nothing or container with colision
|
||||||
|
*/
|
||||||
|
isPortCollision: function (name, containers, port) {
|
||||||
|
var interfaces = {};
|
||||||
|
_.forEach(containers, container => {
|
||||||
|
if (container.Name != name) {
|
||||||
|
_.forEach(this.ports(container), (ip) => {
|
||||||
|
interfaces[ip + ':' + port] = container;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return interfaces[docker.host + ':' + port];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -262,7 +262,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
restart (name) {
|
restart (name) {
|
||||||
this.client.getContainer(name).stop(stopError => {
|
this.client.getContainer(name).stop({t: 10}, stopError => {
|
||||||
if (stopError && stopError.statusCode !== 304) {
|
if (stopError && stopError.statusCode !== 304) {
|
||||||
containerServerActions.error({name, stopError});
|
containerServerActions.error({name, stopError});
|
||||||
return;
|
return;
|
||||||
|
|
@ -278,7 +278,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
stop (name) {
|
stop (name) {
|
||||||
this.client.getContainer(name).stop(error => {
|
this.client.getContainer(name).stop({t: 10}, error => {
|
||||||
if (error && error.statusCode !== 304) {
|
if (error && error.statusCode !== 304) {
|
||||||
containerServerActions.error({name, error});
|
containerServerActions.error({name, error});
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -130,11 +130,9 @@ module.exports = {
|
||||||
}
|
}
|
||||||
if (v1parts[i] === v2parts[i]) {
|
if (v1parts[i] === v2parts[i]) {
|
||||||
continue;
|
continue;
|
||||||
}
|
} else if (v1parts[i] > v2parts[i]) {
|
||||||
else if (v1parts[i] > v2parts[i]) {
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -158,5 +156,5 @@ module.exports = {
|
||||||
linuxToWindowsPath: function (linuxAbsPath) {
|
linuxToWindowsPath: function (linuxAbsPath) {
|
||||||
return linuxAbsPath.replace('/c', 'C:').split('/').join('\\');
|
return linuxAbsPath.replace('/c', 'C:').split('/').join('\\');
|
||||||
},
|
},
|
||||||
webPorts: ['80', '8000', '8080', '3000', '5000', '2368', '9200', '8983']
|
webPorts: ['80', '8000', '8080', '8888', '3000', '5000', '2368', '9200', '8983']
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,27 @@
|
||||||
}
|
}
|
||||||
.table {
|
.table {
|
||||||
&.ports {
|
&.ports {
|
||||||
max-width: 500px;
|
input {
|
||||||
|
width: 50px;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td {
|
||||||
|
&:first-child {
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
&.bind {
|
||||||
|
width: 190px;
|
||||||
|
}
|
||||||
|
&.error {
|
||||||
|
text-align: left;
|
||||||
|
color: red;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&.volumes {
|
&.volumes {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue