mirror of https://github.com/docker/docs.git
Fixes #1206 . In this PR we allow to add new ports using the Ports tab inside the Settings section. It also allows to remove them using the same form.
We include a button with an icon to remove and add ports as needed and the GUI follows the same approach that the one to add environment variables. Signed-off-by: Alexandre Vázquez alexandre.vazquez@gmail.com
This commit is contained in:
parent
d45a012cbe
commit
e6badec155
|
|
@ -5,6 +5,7 @@ import ContainerUtil from '../utils/ContainerUtil';
|
|||
import containerActions from '../actions/ContainerActions';
|
||||
import containerStore from '../stores/ContainerStore';
|
||||
import metrics from '../utils/MetricsUtil';
|
||||
import docker from '../utils/DockerUtil';
|
||||
import {webPorts} from '../utils/Util';
|
||||
import {DropdownButton, MenuItem} from 'react-bootstrap';
|
||||
|
||||
|
|
@ -13,8 +14,18 @@ var ContainerSettingsPorts = React.createClass({
|
|||
router: React.PropTypes.func
|
||||
},
|
||||
getInitialState: function () {
|
||||
var ports = ContainerUtil.ports(this.props.container);
|
||||
var initialPorts = this.props.container.InitialPorts;
|
||||
ports[''] = {
|
||||
ip: docker.host,
|
||||
url: '',
|
||||
port: '',
|
||||
portType: 'tcp',
|
||||
error: null
|
||||
};
|
||||
return {
|
||||
ports: ContainerUtil.ports(this.props.container)
|
||||
ports: ports,
|
||||
initialPorts: initialPorts
|
||||
};
|
||||
},
|
||||
handleViewLink: function (url) {
|
||||
|
|
@ -23,32 +34,67 @@ var ContainerSettingsPorts = React.createClass({
|
|||
});
|
||||
shell.openExternal(url);
|
||||
},
|
||||
handleChangePort: function(key, e) {
|
||||
let ports = this.state.ports;
|
||||
let port = e.target.value;
|
||||
|
||||
// save updated port
|
||||
ports[key] = _.extend(ports[key], {
|
||||
url: 'http://' + ports[key]['ip'] + ':' + port,
|
||||
port: port,
|
||||
error: null
|
||||
});
|
||||
createEmptyPort: function (ports) {
|
||||
ports[''] = {
|
||||
ip: docker.host,
|
||||
url: '',
|
||||
port: '',
|
||||
portType: 'tcp'
|
||||
};
|
||||
document.getElementById('portKey').value = '';
|
||||
document.getElementById('portValue').value = '';
|
||||
},
|
||||
addPort: function () {
|
||||
var portKey = document.getElementById('portKey').value;
|
||||
var portValue = document.getElementById('portValue').value;
|
||||
var portTypeValue = document.getElementById('portType').textContent;
|
||||
var ports = this.state.ports;
|
||||
if (portKey !== '') {
|
||||
ports[portKey] = {
|
||||
ip: docker.host,
|
||||
url: docker.host + ':' + portValue,
|
||||
port: portValue,
|
||||
portType: portTypeValue.trim(),
|
||||
error: null
|
||||
};
|
||||
|
||||
this.checkPort(ports, portKey, portKey);
|
||||
if (ports[portKey].error === null) {
|
||||
this.createEmptyPort(ports);
|
||||
}
|
||||
}
|
||||
return ports;
|
||||
},
|
||||
handleAddPort: function (e) {
|
||||
var ports = this.addPort();
|
||||
this.setState({ports: ports});
|
||||
metrics.track('Added Pending Port');
|
||||
},
|
||||
checkPort: function (ports, port, key) {
|
||||
// basic validation, if number is integer, if its in range, if there
|
||||
// is no collision with ports of other containers and also if there is no
|
||||
// collision with ports for current container
|
||||
const otherContainers = _.filter(_.values(containerStore.getState().containers), c => c.Name !== this.props.container.Name);
|
||||
const otherPorts = _.flatten(otherContainers.map(container => {
|
||||
return _.values(container.NetworkSettings.Ports).map(hosts => hosts.map(host => {
|
||||
return {port: host.HostPort, name: container.Name}
|
||||
}));
|
||||
try {
|
||||
return _.values(container.NetworkSettings.Ports).map(hosts => hosts.map(host => {
|
||||
return {port: host.HostPort, name: container.Name};
|
||||
})
|
||||
);
|
||||
}catch (err) {
|
||||
|
||||
}
|
||||
})).reduce((prev, pair) => {
|
||||
prev[pair.port] = pair.name;
|
||||
try {
|
||||
prev[pair.port] = pair.name;
|
||||
}catch (err) {
|
||||
|
||||
}
|
||||
return prev;
|
||||
}, {});
|
||||
|
||||
const duplicates = _.filter(ports, (v, i) => {
|
||||
return (i != key && _.isEqual(v.port, port));
|
||||
return (i !== key && _.isEqual(v.port, port));
|
||||
});
|
||||
|
||||
if (!port.match(/^[0-9]+$/g)) {
|
||||
|
|
@ -56,12 +102,41 @@ var ContainerSettingsPorts = React.createClass({
|
|||
} else if (port <= 0 || port > 65535) {
|
||||
ports[key].error = 'Needs to be in range <1,65535>.';
|
||||
} else if (otherPorts[port]) {
|
||||
ports[key].error = 'Collision with container "'+ otherPorts[port] +'"';
|
||||
ports[key].error = 'Collision with container "' + otherPorts[port] + '"';
|
||||
} else if (duplicates.length > 0) {
|
||||
ports[key].error = 'Collision with another port in this container.';
|
||||
} else if (port == 22 || port == 2376) {
|
||||
} else if (port === 22 || port === 2376) {
|
||||
ports[key].error = 'Ports 22 and 2376 are reserved ports for Kitematic/Docker.';
|
||||
}
|
||||
},
|
||||
handleChangePort: function (key, e) {
|
||||
let ports = this.state.ports;
|
||||
let port = e.target.value;
|
||||
// save updated port
|
||||
ports[key] = _.extend(ports[key], {
|
||||
url: 'http://' + ports[key].ip + ':' + port,
|
||||
port: port,
|
||||
error: null
|
||||
});
|
||||
this.checkPort(ports, port, key);
|
||||
|
||||
this.setState({ports: ports});
|
||||
},
|
||||
handleChangePortKey: function (key, e) {
|
||||
let ports = this.state.ports;
|
||||
let portKey = e.target.value;
|
||||
|
||||
// save updated port
|
||||
var currentPort = ports[key];
|
||||
|
||||
delete ports[key];
|
||||
ports[portKey] = currentPort;
|
||||
|
||||
this.setState({ports: ports});
|
||||
},
|
||||
handleRemovePort: function (key, e) {
|
||||
let ports = this.state.ports;
|
||||
delete ports[key];
|
||||
this.setState({ports: ports});
|
||||
},
|
||||
handleChangePortType: function (key, portType) {
|
||||
|
|
@ -77,19 +152,38 @@ var ContainerSettingsPorts = React.createClass({
|
|||
});
|
||||
this.setState({ports: ports});
|
||||
},
|
||||
isInitialPort: function (key, ports) {
|
||||
for (var idx in ports) {
|
||||
if (ports.hasOwnProperty(idx)) {
|
||||
var p = idx.split('/');
|
||||
if (p.length > 0) {
|
||||
if (p[0] === key) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
handleSave: function () {
|
||||
let exposedPorts = {};
|
||||
let portBindings = _.reduce(this.state.ports, (res, value, key) => {
|
||||
res[key + '/' + value.portType] = [{
|
||||
HostPort: value.port
|
||||
}];
|
||||
exposedPorts[key] = {};
|
||||
let ports = this.state.ports;
|
||||
ports = this.addPort();
|
||||
this.setState({ports: ports});
|
||||
let bindings = _.reduce(ports, (res, value, key) => {
|
||||
if (key !== '') {
|
||||
res[key + '/' + value.portType] = [{
|
||||
HostPort: value.port
|
||||
}];
|
||||
}
|
||||
return res;
|
||||
}, {});
|
||||
|
||||
let hostConfig = _.extend(this.props.container.HostConfig, {PortBindings: portBindings});
|
||||
containerActions.update(this.props.container.Name, {
|
||||
NetworkSettings: {
|
||||
Ports: bindings
|
||||
}
|
||||
});
|
||||
|
||||
containerActions.update(this.props.container.Name, {ExposedPorts: exposedPorts, HostConfig: hostConfig});
|
||||
},
|
||||
render: function () {
|
||||
if (!this.props.container) {
|
||||
|
|
@ -102,24 +196,37 @@ var ContainerSettingsPorts = React.createClass({
|
|||
var key = pair[0];
|
||||
var {ip, port, url, portType, 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});
|
||||
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});
|
||||
var icon = '';
|
||||
var portKey = '';
|
||||
var portValue = '';
|
||||
if (key === '') {
|
||||
icon = <td><a disabled={isUpdating} onClick={this.handleAddPort.bind(this)} className="only-icon btn btn-positive small"><span className="icon icon-add"></span></a></td>;
|
||||
portKey = <input id={'portKey' + key} type="text" disabled={isUpdating} defaultValue={key} />;
|
||||
portValue = <input id={'portValue' + key} type="text" disabled={isUpdating} defaultValue={port} />;
|
||||
}else {
|
||||
if (this.isInitialPort(key, this.state.initialPorts)) {
|
||||
icon = <td></td>;
|
||||
}else {
|
||||
icon = <td><a disabled={isUpdating} onClick={this.handleRemovePort.bind(this, key)} className="only-icon btn btn-action small"><span className="icon icon-delete"></span></a></td>;
|
||||
}
|
||||
portKey = <input id={'portKey' + key} type="text" onChange={this.handleChangePortKey.bind(this, key)} disabled={isUpdating} defaultValue={key} />;
|
||||
portValue = <input id={'portValue' + key} type="text" onChange={this.handleChangePort.bind(this, key)} disabled={isUpdating} defaultValue={port} />;
|
||||
}
|
||||
return (
|
||||
<tr key={key}>
|
||||
<td>{key}</td>
|
||||
<td>{portKey}</td>
|
||||
<td className="bind">
|
||||
{ipLink}:
|
||||
<input
|
||||
type="text"
|
||||
disabled={isUpdating}
|
||||
onChange={this.handleChangePort.bind(this, key)}
|
||||
defaultValue={port} />
|
||||
{portValue}
|
||||
</td>
|
||||
<td>
|
||||
<DropdownButton bsStyle="primary" title={portType}>
|
||||
<DropdownButton disabled={isUpdating} id= {'portType' + key } bsStyle="primary" title={portType} >
|
||||
<MenuItem onSelect={this.handleChangePortType.bind(this, key, 'tcp')} key={key + '-tcp'}>TCP</MenuItem>
|
||||
<MenuItem onSelect={this.handleChangePortType.bind(this, key, 'udp')} key={key + '-udp'}>UDP</MenuItem>
|
||||
</DropdownButton>
|
||||
</td>
|
||||
{icon}
|
||||
<td className="error">{error}</td>
|
||||
</tr>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ export default {
|
|||
|
||||
startContainer (name) {
|
||||
let container = this.client.getContainer(name);
|
||||
|
||||
container.start((error) => {
|
||||
if (error) {
|
||||
containerServerActions.error({name, error});
|
||||
|
|
@ -117,7 +118,7 @@ export default {
|
|||
if (!containerData.HostConfig || (containerData.HostConfig && !containerData.HostConfig.PortBindings)) {
|
||||
containerData.PublishAllPorts = true;
|
||||
}
|
||||
|
||||
|
||||
if (image.Config.Cmd) {
|
||||
containerData.Cmd = image.Config.Cmd;
|
||||
} else if (!image.Config.Entrypoint) {
|
||||
|
|
@ -148,6 +149,14 @@ export default {
|
|||
containerServerActions.error({name: id, error});
|
||||
} else {
|
||||
container.Name = container.Name.replace('/', '');
|
||||
this.client.getImage(container.Image).inspect((error, image) => {
|
||||
if (error) {
|
||||
containerServerActions.error({name, error});
|
||||
return;
|
||||
}
|
||||
container.InitialPorts = image.Config.ExposedPorts;
|
||||
});
|
||||
|
||||
containerServerActions.updated({container});
|
||||
}
|
||||
});
|
||||
|
|
@ -165,6 +174,13 @@ export default {
|
|||
return;
|
||||
}
|
||||
container.Name = container.Name.replace('/', '');
|
||||
this.client.getImage(container.Image).inspect((error, image) => {
|
||||
if (error) {
|
||||
containerServerActions.error({name, error});
|
||||
return;
|
||||
}
|
||||
container.InitialPorts = image.Config.ExposedPorts;
|
||||
});
|
||||
callback(null, container);
|
||||
});
|
||||
}, (err, containers) => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue