Merge branch 'master' into hub-button

This commit is contained in:
Jeffrey Morgan 2015-04-10 16:35:41 -04:00
commit 53cf2b696b
5 changed files with 90 additions and 83 deletions

View File

@ -1,7 +1,7 @@
[![Build Status](https://travis-ci.org/kitematic/kitematic.svg?branch=master)](https://travis-ci.org/kitematic/kitematic) [![Build Status](https://travis-ci.org/kitematic/kitematic.svg?branch=master)](https://travis-ci.org/kitematic/kitematic)
[![Coverage Status](https://coveralls.io/repos/kitematic/kitematic/badge.svg?branch=master)](https://coveralls.io/r/kitematic/kitematic?branch=master) [![Coverage Status](https://coveralls.io/repos/kitematic/kitematic/badge.svg?branch=master)](https://coveralls.io/r/kitematic/kitematic?branch=master)
[![bitHound Score](https://www.bithound.io/github/kitematic/kitematic/badges/score.svg)](https://www.bithound.io/github/kitematic/kitematic) [![bitHound Score](https://www.bithound.io/github/kitematic/kitematic/badges/score.svg)](https://www.bithound.io/github/kitematic/kitematic)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/kitematic/kitematic?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
![Kitematic Logo](https://cloud.githubusercontent.com/assets/251292/5269258/1b229c3c-7a2f-11e4-96f1-e7baf3c86d73.png) ![Kitematic Logo](https://cloud.githubusercontent.com/assets/251292/5269258/1b229c3c-7a2f-11e4-96f1-e7baf3c86d73.png)
@ -27,9 +27,9 @@ Please read through our [Contributing Guidelines](https://github.com/kitematic/k
## Community ## Community
- For questions on how to use Kitematic, see our [user forum](https://forums.docker.com/c/kitematic). - [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/kitematic/kitematic?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
- Ask questions on our [user forum](https://forums.docker.com/c/kitematic).
- **#kitematic** on IRC. [Join the channel](http://webchat.freenode.net/?channels=%23kitematic&uio=d4). - **#kitematic** on IRC. [Join the channel](http://webchat.freenode.net/?channels=%23kitematic&uio=d4).
- Join the Kitematic [Gitter Channel](https://gitter.im/kitematic/kitematic)
- Follow [@kitematic on Twitter](https://twitter.com/kitematic). - Follow [@kitematic on Twitter](https://twitter.com/kitematic).
- Read and subscribe to [the Kitematic Blog](http://blog.kitematic.com). - Read and subscribe to [the Kitematic Blog](http://blog.kitematic.com).

View File

@ -112,7 +112,7 @@ var ContainerHome = React.createClass({
<ContainerHomePreview /> <ContainerHomePreview />
</div> </div>
<div className="right"> <div className="right">
<ContainerHomeLogs/> <ContainerHomeLogs container={this.props.container}/>
<ContainerHomeFolders container={this.props.container} /> <ContainerHomeFolders container={this.props.container} />
</div> </div>
</div> </div>
@ -138,7 +138,7 @@ var ContainerHome = React.createClass({
<div className="details-panel home"> <div className="details-panel home">
<div className="content"> <div className="content">
<div className="left"> <div className="left">
<ContainerHomeLogs/> <ContainerHomeLogs container={this.props.container}/>
</div> </div>
{right} {right}
</div> </div>

View File

@ -6,51 +6,50 @@ var metrics = require('./Metrics');
var _prevBottom = 0; var _prevBottom = 0;
var ContainerHomeLogs = React.createClass({ module.exports = React.createClass({
mixins: [Router.State, Router.Navigation], mixins: [Router.Navigation],
getInitialState: function () { getInitialState: function () {
return { return {
logs: [] logs: []
}; };
}, },
componentWillReceiveProps: function () {
this.init();
},
componentDidMount: function() { componentDidMount: function() {
this.init(); if (!this.props.container) {
LogStore.on(LogStore.SERVER_LOGS_EVENT, this.updateLogs); return;
}
this.update();
this.scrollToBottom();
LogStore.on(LogStore.SERVER_LOGS_EVENT, this.update);
LogStore.fetch(this.props.container.Name);
}, },
componentWillUnmount: function() { componentWillUnmount: function() {
LogStore.removeListener(LogStore.SERVER_LOGS_EVENT, this.updateLogs); LogStore.detach(this.props.container.Name);
LogStore.removeListener(LogStore.SERVER_LOGS_EVENT, this.update);
}, },
componentDidUpdate: function () { componentDidUpdate: function () {
// Scroll logs to bottom this.scrollToBottom();
},
scrollToBottom: function () {
var parent = $('.logs'); var parent = $('.logs');
if (parent.scrollTop() >= _prevBottom - 50) { if (parent.scrollTop() >= _prevBottom - 50) {
parent.scrollTop(parent[0].scrollHeight - parent.height()); parent.scrollTop(parent[0].scrollHeight - parent.height());
} }
_prevBottom = parent[0].scrollHeight - parent.height(); _prevBottom = parent[0].scrollHeight - parent.height();
}, },
init: function () {
this.updateLogs();
},
updateLogs: function (name) {
if (name && name !== this.getParams().name) {
return;
}
this.setState({
logs: LogStore.logs(this.getParams().name)
});
},
handleClickLogs: function () { handleClickLogs: function () {
metrics.track('Viewed Logs', { metrics.track('Viewed Logs', {
from: 'preview' from: 'preview'
}); });
this.transitionTo('containerLogs', {name: this.getParams().name}); this.transitionTo('containerLogs', {name: this.props.container.Name});
},
update: function () {
this.setState({
logs: LogStore.logs(this.props.container.Name)
});
}, },
render: function () { render: function () {
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 <span key={i} dangerouslySetInnerHTML={{__html: l}}></span>;
}); });
if (logs.length === 0) { if (logs.length === 0) {
logs = "No logs for this container."; logs = "No logs for this container.";
@ -62,11 +61,8 @@ var ContainerHomeLogs = React.createClass({
<div className="logs"> <div className="logs">
{logs} {logs}
</div> </div>
<div className="mini-logs-overlay" onClick={this.handleClickLogs}><span className="icon icon-scale-spread-1"></span><div className="text">View Logs</div></div> <div className="mini-logs-overlay" onClick={this.handleClickLogs}><span className="icon icon-scale-spread-1"></span><div className="text">View Logs</div></div> </div>
</div>
</div> </div>
); );
} }
}); });
module.exports = ContainerHomeLogs;

View File

@ -1,49 +1,46 @@
var $ = require('jquery'); var $ = require('jquery');
var React = require('react/addons'); var React = require('react/addons');
var LogStore = require('./LogStore'); var LogStore = require('./LogStore');
var Router = require('react-router');
var _prevBottom = 0; var _prevBottom = 0;
var ContainerLogs = React.createClass({ module.exports = React.createClass({
mixins: [Router.State],
getInitialState: function () { getInitialState: function () {
return { return {
logs: [] logs: []
}; };
}, },
componentWillReceiveProps: function () {
this.init();
},
componentDidMount: function() { componentDidMount: function() {
this.init(); if (!this.props.container) {
LogStore.on(LogStore.SERVER_LOGS_EVENT, this.updateLogs); return;
}
this.update();
this.scrollToBottom();
LogStore.on(LogStore.SERVER_LOGS_EVENT, this.update);
LogStore.fetch(this.props.container.Name);
}, },
componentWillUnmount: function() { componentWillUnmount: function() {
LogStore.removeListener(LogStore.SERVER_LOGS_EVENT, this.updateLogs); LogStore.detach(this.props.container.Name);
LogStore.removeListener(LogStore.SERVER_LOGS_EVENT, this.update);
}, },
componentDidUpdate: function () { componentDidUpdate: function () {
// Scroll logs to bottom this.scrollToBottom();
},
scrollToBottom: function () {
var parent = $('.details-logs'); var parent = $('.details-logs');
if (parent.scrollTop() >= _prevBottom - 50) { if (parent.scrollTop() >= _prevBottom - 50) {
parent.scrollTop(parent[0].scrollHeight - parent.height()); parent.scrollTop(parent[0].scrollHeight - parent.height());
} }
_prevBottom = parent[0].scrollHeight - parent.height(); _prevBottom = parent[0].scrollHeight - parent.height();
}, },
init: function () { update: function () {
this.updateLogs();
},
updateLogs: function (name) {
if (name && name !== this.getParams().name) {
return;
}
this.setState({ this.setState({
logs: LogStore.logs(this.getParams().name) logs: LogStore.logs(this.props.container.Name)
}); });
}, },
render: function () { render: function () {
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 <span key={i} dangerouslySetInnerHTML={{__html: l}}></span>;
}); });
if (logs.length === 0) { if (logs.length === 0) {
logs = "No logs for this container."; logs = "No logs for this container.";
@ -55,5 +52,3 @@ var ContainerLogs = React.createClass({
); );
} }
}); });
module.exports = ContainerLogs;

View File

@ -2,59 +2,77 @@ var EventEmitter = require('events').EventEmitter;
var assign = require('object-assign'); var assign = require('object-assign');
var Convert = require('ansi-to-html'); var Convert = require('ansi-to-html');
var docker = require('./Docker'); var docker = require('./Docker');
var stream = require('stream');
var _convert = new Convert(); var _convert = new Convert();
var _logs = {}; var _logs = {};
var _streams = {}; var _streams = {};
var LogStore = assign(Object.create(EventEmitter.prototype), { var MAX_LOG_SIZE = 3000;
module.exports = assign(Object.create(EventEmitter.prototype), {
SERVER_LOGS_EVENT: 'server_logs_event', SERVER_LOGS_EVENT: 'server_logs_event',
_escapeHTML: function (html) { _escape: function (html) {
var text = document.createTextNode(html); var text = document.createTextNode(html);
var div = document.createElement('div'); var div = document.createElement('div');
div.appendChild(text); div.appendChild(text);
return div.innerHTML; return div.innerHTML;
}, },
fetchLogs: function (name) { fetch: function (name) {
if (!name || !docker.client()) { if (!name || !docker.client()) {
return; return;
} }
var index = 0;
var self = this;
docker.client().getContainer(name).logs({ docker.client().getContainer(name).logs({
follow: true,
stdout: true, stdout: true,
stderr: true, stderr: true,
timestamps: true timestamps: false,
}, function (err, stream) { tail: MAX_LOG_SIZE,
if (_streams[name]) { follow: false
return; }, (err, logStream) => {
}
_streams[name] = stream;
if (err) { if (err) {
return; throw err;
} }
_logs[name] = []; var logs = [];
stream.setEncoding('utf8'); logStream.setEncoding('utf-8');
stream.on('data', function (buf) { logStream.on('data', (chunk) => {
// Every other message is a header logs.push(_convert.toHtml(this._escape(chunk)));
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);
}
index += 1;
}); });
stream.on('end', function () { logStream.on('end', () => {
delete _streams[name]; _logs[name] = logs;
this.emit(this.SERVER_LOGS_EVENT);
this.attach(name);
}); });
}); });
}, },
logs: function (name) { attach: function (name) {
if (!_streams[name]) { if (!name || !docker.client() || _streams[name]) {
this.fetchLogs(name); return;
} }
docker.client().getContainer(name).attach({
stdout: true,
stderr: true,
logs: false,
stream: true
}, (err, logStream) => {
if (err) {
throw err;
}
logStream.setEncoding('utf-8');
logStream.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);
});
});
},
detach: function (name) {
if (_streams[name]) {
_streams[name].destroy();
}
},
logs: function (name) {
return _logs[name] || []; return _logs[name] || [];
}, },
rename: function (name, newName) { rename: function (name, newName) {
@ -63,5 +81,3 @@ var LogStore = assign(Object.create(EventEmitter.prototype), {
} }
} }
}); });
module.exports = LogStore;