mirror of https://github.com/docker/docs.git
Merge pull request #165 from kitematic/container-dropdowns
Added drop down settings for container ports and volumes.
This commit is contained in:
commit
e00454f1ba
|
@ -4,6 +4,7 @@ var React = require('react/addons');
|
|||
var Router = require('react-router');
|
||||
var exec = require('exec');
|
||||
var path = require('path');
|
||||
var assign = require('object-assign');
|
||||
var remote = require('remote');
|
||||
var dialog = remote.require('dialog');
|
||||
var ContainerStore = require('./ContainerStore');
|
||||
|
@ -13,7 +14,7 @@ var boot2docker = require('./boot2docker');
|
|||
var ProgressBar = require('react-bootstrap/ProgressBar');
|
||||
|
||||
var ContainerDetails = React.createClass({
|
||||
mixins: [Router.State],
|
||||
mixins: [Router.State, Router.Navigation],
|
||||
_oldHeight: 0,
|
||||
PAGE_LOGS: 'logs',
|
||||
PAGE_SETTINGS: 'settings',
|
||||
|
@ -22,7 +23,12 @@ var ContainerDetails = React.createClass({
|
|||
logs: [],
|
||||
page: this.PAGE_LOGS,
|
||||
env: {},
|
||||
pendingEnv: {}
|
||||
pendingEnv: {},
|
||||
ports: {},
|
||||
defaultPort: null,
|
||||
volumes: {},
|
||||
popoverVolumeOpen: false,
|
||||
popoverViewOpen: false,
|
||||
};
|
||||
},
|
||||
componentWillReceiveProps: function () {
|
||||
|
@ -34,29 +40,72 @@ var ContainerDetails = React.createClass({
|
|||
this.init();
|
||||
ContainerStore.on(ContainerStore.SERVER_PROGRESS_EVENT, this.updateProgress);
|
||||
ContainerStore.on(ContainerStore.SERVER_LOGS_EVENT, this.updateLogs);
|
||||
|
||||
// Make clicking anywhere close popovers
|
||||
$('body').on('click', function (e) {
|
||||
var popoverViewIsTarget = $('.popover-view').is(e.target) || $('.popover-view').has(e.target).length !== 0 || $('.dropdown-view').is(e.target) || $('.dropdown-view').has(e.target).length !== 0;
|
||||
var popoverVolumeIsTarget = $('.popover-volume').is(e.target) || $('.popover-volume').has(e.target).length !== 0 || $('.dropdown-volume').is(e.target) || $('.dropdown-volume').has(e.target).length !== 0;
|
||||
var state = {};
|
||||
if (!popoverViewIsTarget) {
|
||||
state.popoverViewOpen = false;
|
||||
}
|
||||
if (!popoverVolumeIsTarget) {
|
||||
state.popoverVolumeOpen = false;
|
||||
}
|
||||
if (this.state.popoverViewOpen || this.state.popoverVolumeOpen) {
|
||||
this.setState(state);
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
componentWillUnmount: function () {
|
||||
ContainerStore.removeListener(ContainerStore.SERVER_PROGRESS_EVENT, this.updateProgress);
|
||||
ContainerStore.removeListener(ContainerStore.SERVER_LOGS_EVENT, this.updateLogs);
|
||||
},
|
||||
componentDidUpdate: function () {
|
||||
// Scroll logs to bottom
|
||||
var parent = $('.details-logs');
|
||||
if (!parent.length) {
|
||||
return;
|
||||
if (parent.length) {
|
||||
if (parent.scrollTop() >= this._oldHeight) {
|
||||
parent.stop();
|
||||
parent.scrollTop(parent[0].scrollHeight - parent.height());
|
||||
}
|
||||
this._oldHeight = parent[0].scrollHeight - parent.height();
|
||||
}
|
||||
if (parent.scrollTop() >= this._oldHeight) {
|
||||
parent.stop();
|
||||
parent.scrollTop(parent[0].scrollHeight - parent.height());
|
||||
|
||||
var $viewDropdown = $(this.getDOMNode()).find('.dropdown-view');
|
||||
var $volumeDropdown = $(this.getDOMNode()).find('.dropdown-volume');
|
||||
var $viewPopover = $(this.getDOMNode()).find('.popover-view');
|
||||
var $volumePopover = $(this.getDOMNode()).find('.popover-volume');
|
||||
|
||||
if ($viewDropdown && $volumeDropdown && $viewPopover && $volumePopover) {
|
||||
$viewPopover.offset({
|
||||
top: $viewDropdown.offset().top + 32,
|
||||
left: $viewDropdown.offset().left - ($viewPopover.outerWidth() / 2) + 14
|
||||
});
|
||||
|
||||
$volumePopover.offset({
|
||||
top: $volumeDropdown.offset().top + 32,
|
||||
left: $volumeDropdown.offset().left + $volumeDropdown.outerWidth() - $volumePopover.outerWidth() / 2 - 20
|
||||
});
|
||||
}
|
||||
this._oldHeight = parent[0].scrollHeight - parent.height();
|
||||
},
|
||||
init: function () {
|
||||
var container = ContainerStore.container(this.getParams().name);
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
env: ContainerUtil.env(ContainerStore.container(this.getParams().name))
|
||||
env: ContainerUtil.env(container),
|
||||
});
|
||||
ContainerStore.fetchLogs(this.getParams().name, function () {
|
||||
this.updateLogs();
|
||||
}.bind(this));
|
||||
var ports = ContainerUtil.ports(container);
|
||||
var webPorts = ['80', '8000', '8080', '3000', '5000', '2368'];
|
||||
this.setState({
|
||||
ports: ports,
|
||||
defaultPort: _.find(_.keys(ports), function (port) {
|
||||
return webPorts.indexOf(port) !== -1;
|
||||
})
|
||||
});
|
||||
this.updateLogs();
|
||||
},
|
||||
updateLogs: function (name) {
|
||||
if (name && name !== this.getParams().name) {
|
||||
|
@ -84,27 +133,31 @@ var ContainerDetails = React.createClass({
|
|||
});
|
||||
},
|
||||
handleView: function () {
|
||||
var container = this.props.container;
|
||||
boot2docker.ip(function (err, ip) {
|
||||
var ports = _.map(container.NetworkSettings.Ports, function (value, key) {
|
||||
var portProtocolPair = key.split('/');
|
||||
var res = {
|
||||
'port': portProtocolPair[0],
|
||||
'protocol': portProtocolPair[1]
|
||||
};
|
||||
if (value && value.length) {
|
||||
var port = value[0].HostPort;
|
||||
res.host = ip;
|
||||
res.port = port;
|
||||
res.url = 'http://' + ip + ':' + port;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return res;
|
||||
});
|
||||
exec(['open', ports[0].url], function (err) {
|
||||
if (this.state.defaultPort) {
|
||||
console.log(this.state.defaultPort);
|
||||
exec(['open', this.state.ports[this.state.defaultPort].url], function (err) {
|
||||
if (err) { throw err; }
|
||||
});
|
||||
}
|
||||
},
|
||||
handleViewLink: function (url) {
|
||||
exec(['open', url], function (err) {
|
||||
if (err) { throw err; }
|
||||
});
|
||||
},
|
||||
handleViewDropdown: function(e) {
|
||||
this.setState({
|
||||
popoverViewOpen: !this.state.popoverViewOpen
|
||||
});
|
||||
},
|
||||
handleVolumeDropdown: function(e) {
|
||||
this.setState({
|
||||
popoverVolumeOpen: !this.state.popoverVolumeOpen
|
||||
});
|
||||
},
|
||||
handleRestart: function () {
|
||||
ContainerStore.restart(this.props.container.Name, function (err) {
|
||||
console.log(err);
|
||||
});
|
||||
},
|
||||
handleRestart: function () {
|
||||
|
@ -122,6 +175,17 @@ var ContainerDetails = React.createClass({
|
|||
}
|
||||
});
|
||||
},
|
||||
handleSaveContainerName: function () {
|
||||
var newName = $('#input-container-name').val();
|
||||
ContainerStore.updateContainer(this.props.container.Name, {
|
||||
name: newName
|
||||
}, function (err) {
|
||||
this.transitionTo('container', {name: newName});
|
||||
if (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
handleSaveEnvVar: function () {
|
||||
var $rows = $('.env-vars .keyval-row');
|
||||
var envVarList = [];
|
||||
|
@ -133,8 +197,19 @@ var ContainerDetails = React.createClass({
|
|||
}
|
||||
envVarList.push(key + '=' + val);
|
||||
});
|
||||
ContainerStore.updateContainer(this.props.container.Name, {
|
||||
var self = this;
|
||||
ContainerStore.updateContainer(self.props.container.Name, {
|
||||
Env: envVarList
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
} else {
|
||||
self.setState({
|
||||
pendingEnv: {}
|
||||
});
|
||||
$('#new-env-key').val('');
|
||||
$('#new-env-val').val('');
|
||||
}
|
||||
});
|
||||
},
|
||||
handleAddPendingEnvVar: function () {
|
||||
|
@ -224,6 +299,66 @@ var ContainerDetails = React.createClass({
|
|||
);
|
||||
});
|
||||
|
||||
var disabledClass = '';
|
||||
if (!this.props.container.State.Running) {
|
||||
disabledClass = 'disabled';
|
||||
}
|
||||
|
||||
var buttonClass = React.addons.classSet({
|
||||
btn: true,
|
||||
'btn-action': true,
|
||||
'with-icon': true,
|
||||
disabled: !this.props.container.State.Running
|
||||
});
|
||||
|
||||
var viewButtonClass = React.addons.classSet({
|
||||
btn: true,
|
||||
'btn-action': true,
|
||||
'with-icon': true,
|
||||
disabled: !this.props.container.State.Running || !this.state.defaultPort
|
||||
});
|
||||
|
||||
var textButtonClasses = React.addons.classSet({
|
||||
'btn': true,
|
||||
'btn-action': true,
|
||||
'only-icon': true,
|
||||
'active': this.state.page === this.PAGE_LOGS,
|
||||
disabled: this.props.container.State.Downloading
|
||||
});
|
||||
|
||||
var gearButtonClass = React.addons.classSet({
|
||||
'btn': true,
|
||||
'btn-action': true,
|
||||
'only-icon': true,
|
||||
'active': this.state.page === this.PAGE_SETTINGS,
|
||||
disabled: this.props.container.State.Downloading
|
||||
});
|
||||
|
||||
var viewPopoverClasses = React.addons.classSet({
|
||||
popover: true,
|
||||
hidden: false
|
||||
});
|
||||
|
||||
var popoverVolumeClasses = React.addons.classSet({
|
||||
'popover-volume': true,
|
||||
hidden: !this.state.popoverVolumeOpen
|
||||
});
|
||||
|
||||
var popoverViewClasses = React.addons.classSet({
|
||||
'popover-view': true,
|
||||
hidden: !this.state.popoverViewOpen
|
||||
});
|
||||
|
||||
var dropdownClasses = {
|
||||
btn: true,
|
||||
'btn-action': true,
|
||||
'with-icon': true,
|
||||
'dropdown-toggle': true,
|
||||
disabled: !this.props.container.State.Running
|
||||
};
|
||||
var dropdownViewButtonClass = React.addons.classSet(assign({'dropdown-view': true}, dropdownClasses));
|
||||
var dropdownVolumeButtonClass = React.addons.classSet(assign({'dropdown-volume': true}, dropdownClasses));
|
||||
|
||||
var body;
|
||||
if (this.props.container.State.Downloading) {
|
||||
body = (
|
||||
|
@ -245,7 +380,10 @@ var ContainerDetails = React.createClass({
|
|||
<div className="details-panel">
|
||||
<div className="settings">
|
||||
<h3>Container Name</h3>
|
||||
<input id="input-container-name" type="text" className="line" placeholder="Container Name" defaultValue={this.props.container.Name}></input>
|
||||
<div className="container-name">
|
||||
<input id="input-container-name" type="text" className="line" placeholder="Container Name" defaultValue={this.props.container.Name}></input>
|
||||
</div>
|
||||
<a className="btn btn-action" onClick={this.handleSaveContainerName}>Save</a>
|
||||
<h3>Environment Variables</h3>
|
||||
<div className="env-vars-labels">
|
||||
<div className="label-key">KEY</div>
|
||||
|
@ -269,38 +407,27 @@ var ContainerDetails = React.createClass({
|
|||
}
|
||||
}
|
||||
|
||||
var disabledClass = '';
|
||||
if (!this.props.container.State.Running) {
|
||||
disabledClass = 'disabled';
|
||||
}
|
||||
|
||||
var buttonClass = React.addons.classSet({
|
||||
btn: true, 'btn-action': true,
|
||||
'with-icon': true,
|
||||
disabled: !this.props.container.State.Running
|
||||
});
|
||||
var dropdownButtonClass = React.addons.classSet({
|
||||
btn: true,
|
||||
'btn-action': true,
|
||||
'with-icon': true,
|
||||
'dropdown-toggle': true,
|
||||
disabled: !this.props.container.State.Running
|
||||
var ports = _.map(_.pairs(self.state.ports), function (pair, index, list) {
|
||||
var key = pair[0];
|
||||
var val = pair[1];
|
||||
return (
|
||||
<div key={key} className="table-values">
|
||||
<span className="value-left">{key}</span><span className="icon icon-arrow-right"></span>
|
||||
<a className="value-right" onClick={self.handleViewLink.bind(self, val.url)}>{val.display}</a>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
var textButtonClasses = React.addons.classSet({
|
||||
'btn': true,
|
||||
'btn-action': true,
|
||||
'only-icon': true,
|
||||
'active': this.state.page === this.PAGE_LOGS,
|
||||
disabled: this.props.container.State.Downloading
|
||||
});
|
||||
|
||||
var gearButtonClass = React.addons.classSet({
|
||||
'btn': true,
|
||||
'btn-action': true,
|
||||
'only-icon': true,
|
||||
'active': this.state.page === this.PAGE_SETTINGS,
|
||||
disabled: this.props.container.State.Downloading
|
||||
var volumes = _.map(self.props.container.Volumes, function (val, key) {
|
||||
if (!val || val.indexOf(process.env.HOME) === -1) {
|
||||
val = 'No Host Folder';
|
||||
}
|
||||
return (
|
||||
<div key={key} className="table-values">
|
||||
<span className="value-left">{key}</span><span className="icon icon-arrow-right"></span>
|
||||
<a className="value-right">{val.replace(process.env.HOME, '~')}</a>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
|
@ -311,10 +438,11 @@ var ContainerDetails = React.createClass({
|
|||
</div>
|
||||
<div className="details-header-actions">
|
||||
<div className="action btn-group">
|
||||
<a className={buttonClass} onClick={this.handleView}><span className="icon icon-preview-2"></span><span className="content">View</span></a><a className={dropdownButtonClass}><span className="icon-dropdown icon icon-arrow-37"></span></a>
|
||||
<a className={viewButtonClass} onClick={this.handleView}><span className="icon icon-preview-2"></span><span className="content">View</span></a>
|
||||
<a className={dropdownViewButtonClass} onClick={this.handleViewDropdown}><span className="icon-dropdown icon icon-arrow-37"></span></a>
|
||||
</div>
|
||||
<div className="action">
|
||||
<a className={dropdownButtonClass} onClick={this.handleView}><span className="icon icon-folder-1"></span> <span className="content">Volumes</span> <span className="icon-dropdown icon icon-arrow-37"></span></a>
|
||||
<a className={dropdownVolumeButtonClass} onClick={this.handleVolumeDropdown}><span className="icon icon-folder-1"></span> <span className="content">Volumes</span> <span className="icon-dropdown icon icon-arrow-37"></span></a>
|
||||
</div>
|
||||
<div className="action">
|
||||
<a className={buttonClass} onClick={this.handleRestart}><span className="icon icon-refresh"></span> <span className="content">Restart</span></a>
|
||||
|
@ -327,6 +455,24 @@ var ContainerDetails = React.createClass({
|
|||
<a className={gearButtonClass} onClick={this.showSettings}><span className="icon icon-setting-gear"></span></a>
|
||||
</div>
|
||||
</div>
|
||||
<Popover className={popoverViewClasses} placement="bottom">
|
||||
<div className="table ports">
|
||||
<div className="table-labels">
|
||||
<div className="label-left">DOCKER PORT</div>
|
||||
<div className="label-right">MAC PORT</div>
|
||||
</div>
|
||||
{ports}
|
||||
</div>
|
||||
</Popover>
|
||||
<Popover className={popoverVolumeClasses} placement="bottom">
|
||||
<div className="table volumes">
|
||||
<div className="table-labels">
|
||||
<div className="label-left">DOCKER FOLDER</div>
|
||||
<div className="label-right">MAC FOLDER</div>
|
||||
</div>
|
||||
{volumes}
|
||||
</div>
|
||||
</Popover>
|
||||
</div>
|
||||
{body}
|
||||
</div>
|
||||
|
|
|
@ -41,6 +41,10 @@ var ContainerModal = React.createClass({
|
|||
this._searchRequest = null;
|
||||
}
|
||||
|
||||
if (!query.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
loading: true
|
||||
});
|
||||
|
@ -227,6 +231,7 @@ var ContainerModal = React.createClass({
|
|||
<div className={magnifierClasses}></div>
|
||||
<RetinaImage className={loadingClasses} src="loading.png"/>
|
||||
</div>
|
||||
{question}
|
||||
<div className="results">
|
||||
<div className="title">{title}</div>
|
||||
{results}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
var EventEmitter = require('events').EventEmitter;
|
||||
var async = require('async');
|
||||
var path = require('path');
|
||||
var assign = require('object-assign');
|
||||
var Stream = require('stream');
|
||||
var Convert = require('ansi-to-html');
|
||||
|
@ -15,7 +16,9 @@ var _recommended = [];
|
|||
var _containers = {};
|
||||
var _progress = {};
|
||||
var _logs = {};
|
||||
var _streams = {};
|
||||
var _muted = {};
|
||||
var _config = {};
|
||||
|
||||
var ContainerStore = assign(EventEmitter.prototype, {
|
||||
CLIENT_CONTAINER_EVENT: 'client_container',
|
||||
|
@ -110,30 +113,44 @@ var ContainerStore = assign(EventEmitter.prototype, {
|
|||
_createContainer: function (name, containerData, callback) {
|
||||
var existing = docker.client().getContainer(name);
|
||||
var self = this;
|
||||
if (!containerData.name) containerData.name = containerData.Name;
|
||||
if (!containerData.name && containerData.Name) {
|
||||
containerData.name = containerData.Name;
|
||||
} else if (!containerData.name) {
|
||||
containerData.name = name;
|
||||
}
|
||||
if (containerData.Config && containerData.Config.Image) {
|
||||
containerData.Image = containerData.Config.Image;
|
||||
}
|
||||
existing.kill(function (err, data) {
|
||||
existing.remove(function (err, data) {
|
||||
docker.client().createContainer(containerData, function (err, container) {
|
||||
if (err) {
|
||||
callback(err, null);
|
||||
return;
|
||||
}
|
||||
if (containerData.State && !containerData.State.Running) {
|
||||
self.fetchContainer(name, callback);
|
||||
} else {
|
||||
container.start({
|
||||
PublishAllPorts: true
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
self.fetchContainer(name, callback);
|
||||
docker.client().getImage(containerData.Image).inspect(function (err, data) {
|
||||
var binds = [];
|
||||
if (data.Config.Volumes) {
|
||||
_.each(data.Config.Volumes, function (value, key) {
|
||||
binds.push(path.join(process.env.HOME, 'Kitematic', containerData.name, key)+ ':' + key);
|
||||
});
|
||||
}
|
||||
|
||||
docker.client().createContainer(containerData, function (err, container) {
|
||||
if (err) {
|
||||
callback(err, null);
|
||||
return;
|
||||
}
|
||||
if (containerData.State && !containerData.State.Running) {
|
||||
self.fetchContainer(containerData.name, callback);
|
||||
} else {
|
||||
container.start({
|
||||
PublishAllPorts: true,
|
||||
Binds: binds
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
self.fetchContainer(containerData.name, callback);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -209,10 +226,13 @@ var ContainerStore = assign(EventEmitter.prototype, {
|
|||
// If the event is delete, remove the container
|
||||
if (data.status === 'destroy') {
|
||||
var container = _.findWhere(_.values(_containers), {Id: data.id});
|
||||
if (!container || _muted[container.Name]) {
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
delete _containers[container.Name];
|
||||
if (_muted[container.Name]) {
|
||||
return;
|
||||
}
|
||||
this.emit(this.SERVER_CONTAINER_EVENT, container.Name, data.status);
|
||||
} else {
|
||||
this.fetchContainer(data.id, function (err) {
|
||||
|
@ -256,10 +276,13 @@ var ContainerStore = assign(EventEmitter.prototype, {
|
|||
container.State.Downloading = !!env.KITEMATIC_DOWNLOADING;
|
||||
container.KitematicDownloadingImage = env.KITEMATIC_DOWNLOADING_IMAGE;
|
||||
|
||||
this.fetchLogs(container.Name, function (err) {
|
||||
}.bind(this));
|
||||
|
||||
_containers[container.Name] = container;
|
||||
callback(null, container);
|
||||
}
|
||||
});
|
||||
}.bind(this));
|
||||
},
|
||||
fetchAllContainers: function (callback) {
|
||||
var self = this;
|
||||
|
@ -305,57 +328,45 @@ var ContainerStore = assign(EventEmitter.prototype, {
|
|||
});
|
||||
},
|
||||
fetchLogs: function (name, callback) {
|
||||
if (_logs[name]) {
|
||||
callback();
|
||||
} else {
|
||||
_logs[name] = [];
|
||||
}
|
||||
var index = 0;
|
||||
var self = this;
|
||||
docker.client().getContainer(name).logs({
|
||||
follow: false,
|
||||
follow: true,
|
||||
stdout: true,
|
||||
stderr: true,
|
||||
timestamps: true
|
||||
}, function (err, stream) {
|
||||
callback(err);
|
||||
if (_streams[name]) {
|
||||
return;
|
||||
}
|
||||
_streams[name] = stream;
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
_logs[name] = [];
|
||||
stream.setEncoding('utf8');
|
||||
var timeout;
|
||||
stream.on('data', function (buf) {
|
||||
// Every other message is a header
|
||||
if (index % 2 === 1) {
|
||||
var time = buf.substr(0,buf.indexOf(' '));
|
||||
var msg = buf.substr(buf.indexOf(' ')+1);
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = null;
|
||||
}
|
||||
timeout = setTimeout(function () {
|
||||
timeout = null;
|
||||
self.emit(self.SERVER_LOGS_EVENT, name);
|
||||
}, 100);
|
||||
_logs[name].push(convert.toHtml(self._escapeHTML(msg)));
|
||||
}
|
||||
index += 1;
|
||||
});
|
||||
stream.on('end', function (buf) {
|
||||
self.emit(self.SERVER_LOGS_EVENT, name);
|
||||
callback();
|
||||
docker.client().getContainer(name).logs({
|
||||
follow: true,
|
||||
stdout: true,
|
||||
stderr: true,
|
||||
timestamps: true,
|
||||
tail: 0
|
||||
}, function (err, stream) {
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
stream.setEncoding('utf8');
|
||||
stream.on('data', function (buf) {
|
||||
// Every other message is a header
|
||||
if (index % 2 === 1) {
|
||||
var time = buf.substr(0,buf.indexOf(' '));
|
||||
var msg = buf.substr(buf.indexOf(' ')+1);
|
||||
_logs[name].push(convert.toHtml(self._escapeHTML(msg)));
|
||||
self.emit(self.SERVER_LOGS_EVENT, name);
|
||||
}
|
||||
index += 1;
|
||||
});
|
||||
});
|
||||
stream.on('end', function () {
|
||||
delete _streams[name];
|
||||
console.log('end', name);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
@ -395,11 +406,15 @@ var ContainerStore = assign(EventEmitter.prototype, {
|
|||
}
|
||||
});
|
||||
},
|
||||
updateContainer: function (name, data) {
|
||||
updateContainer: function (name, data, callback) {
|
||||
_muted[name] = true;
|
||||
if (!data.name) {
|
||||
data.name = data.Name;
|
||||
}
|
||||
var fullData = assign(_containers[name], data);
|
||||
console.log(fullData);
|
||||
this._createContainer(name, fullData, function (err) {
|
||||
this.emit(this.CLIENT_CONTAINER_EVENT, name);
|
||||
callback(err);
|
||||
_muted[name] = false;
|
||||
}.bind(this));
|
||||
},
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
var _ = require('underscore');
|
||||
var docker = require('./docker');
|
||||
|
||||
var ContainerUtil = {
|
||||
env: function (container) {
|
||||
|
@ -10,6 +11,26 @@ var ContainerUtil = {
|
|||
var splits = [env.slice(0, i), env.slice(i + 1)];
|
||||
return splits;
|
||||
}));
|
||||
},
|
||||
ports: function (container, callback) {
|
||||
var res = {};
|
||||
var ip = docker.host;
|
||||
console.log(container);
|
||||
_.each(container.NetworkSettings.Ports, function (value, key) {
|
||||
var dockerPort = key;
|
||||
var localUrl = null;
|
||||
var localUrlDisplay = null;
|
||||
if (value && value.length) {
|
||||
var port = value[0].HostPort;
|
||||
localUrl = 'http://' + ip + ':' + port;
|
||||
localUrlDisplay = ip + ': ' + port;
|
||||
}
|
||||
res[dockerPort] = {
|
||||
url: localUrl,
|
||||
display: localUrlDisplay
|
||||
};
|
||||
});
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -125,7 +125,7 @@ var setupSteps = [
|
|||
boot2docker.ip(function (err, ip) {
|
||||
if (err) {callback(err); return;}
|
||||
console.log('Setting host IP to: ' + ip);
|
||||
// Docker.setHost(ip);
|
||||
docker.setHost(ip);
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
|
@ -147,6 +147,8 @@ var Setup = React.createClass({
|
|||
radial = <Radial progress={this.state.progress}/>;
|
||||
} else if (this.state.error) {
|
||||
radial = <Radial error={true} spin="true" progress="100"/>;
|
||||
} else {
|
||||
radial = <Radial spin="true" progress="100"/>;
|
||||
}
|
||||
if (this.state.error) {
|
||||
return (
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.create-modal {
|
||||
@modal-padding: 32px;
|
||||
@search-width: 372px;
|
||||
@custom-width: 270px;
|
||||
@custom-width: 0;
|
||||
.modal-dialog {
|
||||
margin-top: 80px;
|
||||
width: calc(@modal-padding + @search-width + 2 * @modal-padding + @custom-width);
|
||||
|
@ -20,12 +20,6 @@
|
|||
flex-direction: row;
|
||||
padding: 32px 32px;
|
||||
|
||||
aside.custom {
|
||||
flex: 0 auto;
|
||||
padding-left: 32px;
|
||||
min-width: 270px;
|
||||
}
|
||||
|
||||
.popover {
|
||||
width: 180px;
|
||||
text-align: center;
|
||||
|
@ -62,12 +56,8 @@
|
|||
|
||||
section.search {
|
||||
min-width: 404px;
|
||||
padding-right: 32px;
|
||||
border-right: 1px solid #eee;
|
||||
|
||||
.question {
|
||||
color: @gray-lightest;
|
||||
font-size: 10px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,119 @@
|
|||
.popover {
|
||||
|
||||
&.popover-view {
|
||||
min-width: 290px;
|
||||
}
|
||||
|
||||
&.popover-volume {
|
||||
min-width: 400px;
|
||||
}
|
||||
|
||||
.popover-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 14px 14px 20px;
|
||||
|
||||
.table {
|
||||
margin-bottom: 0;
|
||||
.icon-arrow-right {
|
||||
color: #aaa;
|
||||
margin: 2px 9px 0;
|
||||
flex: 0 auto;
|
||||
min-width: 13px;
|
||||
}
|
||||
.btn {
|
||||
min-width: 22px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.table-labels {
|
||||
flex: 1 auto;
|
||||
display: flex;
|
||||
font-size: 12px;
|
||||
color: @gray-lightest;
|
||||
.label-left {
|
||||
flex: 0 auto;
|
||||
min-width: 80px;
|
||||
margin-right: 30px;
|
||||
text-align: right;
|
||||
}
|
||||
.label-right {
|
||||
flex: 1 auto;
|
||||
display: inline-block;
|
||||
width: 40%;
|
||||
}
|
||||
}
|
||||
.table-values {
|
||||
flex: 1 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 8px 0;
|
||||
.value-left {
|
||||
text-align: right;
|
||||
min-width: 80px;
|
||||
flex: 0 auto;
|
||||
}
|
||||
.value-right {
|
||||
flex: 1 auto;
|
||||
-webkit-user-select: text;
|
||||
width: 154px;
|
||||
}
|
||||
}
|
||||
.table-new {
|
||||
margin-top: 10px;
|
||||
flex: 1 auto;
|
||||
display: flex;
|
||||
input {
|
||||
padding: 0;
|
||||
font-weight: 400;
|
||||
}
|
||||
input.new-left {
|
||||
flex: 0 auto;
|
||||
text-align: right;
|
||||
min-width: 80px;
|
||||
max-width: 80px;
|
||||
}
|
||||
.new-right-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 1 auto;
|
||||
.new-right-placeholder {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 0;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
input.new-right {
|
||||
flex: 1 auto;
|
||||
height: 24px;
|
||||
position :relative;
|
||||
padding-left: 107px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.volumes {
|
||||
.label-left {
|
||||
min-width: 120px;
|
||||
}
|
||||
.value-left {
|
||||
min-width: 120px;
|
||||
}
|
||||
.icon {
|
||||
color: #aaa;
|
||||
margin: 2px 9px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.question {
|
||||
margin: 12px 6px 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.containers {
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -349,6 +464,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
.container-name {
|
||||
margin-bottom: 20px;
|
||||
input {
|
||||
width: 20%;
|
||||
}
|
||||
}
|
||||
|
||||
.env-vars-labels {
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
|
|
|
@ -48,6 +48,11 @@ html, body {
|
|||
}
|
||||
}
|
||||
|
||||
.question {
|
||||
color: @gray-lightest;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.popover {
|
||||
font-family: 'Clear Sans', sans-serif;
|
||||
color: @gray-normal;
|
||||
|
|
|
@ -17,6 +17,7 @@ if (argv.test) {
|
|||
}
|
||||
|
||||
process.env.NODE_PATH = __dirname + '/../node_modules';
|
||||
process.chdir(path.join(__dirname, '..'));
|
||||
|
||||
app.on('activate-with-no-open-windows', function () {
|
||||
if (mainWindow) {
|
||||
|
@ -98,7 +99,6 @@ app.on('ready', function() {
|
|||
});
|
||||
|
||||
ipc.on('vm', function (event, arg) {
|
||||
console.log('save vm', arg);
|
||||
saveVMOnQuit = arg;
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue