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)
[![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)
[![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)
@ -27,9 +27,9 @@ Please read through our [Contributing Guidelines](https://github.com/kitematic/k
## 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).
- 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).

View File

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

View File

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

View File

@ -1,49 +1,46 @@
var $ = require('jquery');
var React = require('react/addons');
var LogStore = require('./LogStore');
var Router = require('react-router');
var _prevBottom = 0;
var ContainerLogs = React.createClass({
mixins: [Router.State],
module.exports = React.createClass({
getInitialState: function () {
return {
logs: []
};
},
componentWillReceiveProps: function () {
this.init();
},
componentDidMount: function() {
this.init();
LogStore.on(LogStore.SERVER_LOGS_EVENT, this.updateLogs);
if (!this.props.container) {
return;
}
this.update();
this.scrollToBottom();
LogStore.on(LogStore.SERVER_LOGS_EVENT, this.update);
LogStore.fetch(this.props.container.Name);
},
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 () {
// Scroll logs to bottom
this.scrollToBottom();
},
scrollToBottom: function () {
var parent = $('.details-logs');
if (parent.scrollTop() >= _prevBottom - 50) {
parent.scrollTop(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;
}
update: function () {
this.setState({
logs: LogStore.logs(this.getParams().name)
logs: LogStore.logs(this.props.container.Name)
});
},
render: function () {
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) {
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 Convert = require('ansi-to-html');
var docker = require('./Docker');
var stream = require('stream');
var _convert = new Convert();
var _logs = {};
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',
_escapeHTML: function (html) {
_escape: function (html) {
var text = document.createTextNode(html);
var div = document.createElement('div');
div.appendChild(text);
return div.innerHTML;
},
fetchLogs: function (name) {
fetch: function (name) {
if (!name || !docker.client()) {
return;
}
var index = 0;
var self = this;
docker.client().getContainer(name).logs({
follow: true,
stdout: true,
stderr: true,
timestamps: true
}, function (err, stream) {
if (_streams[name]) {
return;
}
_streams[name] = stream;
timestamps: false,
tail: MAX_LOG_SIZE,
follow: false
}, (err, logStream) => {
if (err) {
return;
throw err;
}
_logs[name] = [];
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);
}
index += 1;
var logs = [];
logStream.setEncoding('utf-8');
logStream.on('data', (chunk) => {
logs.push(_convert.toHtml(this._escape(chunk)));
});
stream.on('end', function () {
delete _streams[name];
logStream.on('end', () => {
_logs[name] = logs;
this.emit(this.SERVER_LOGS_EVENT);
this.attach(name);
});
});
},
logs: function (name) {
if (!_streams[name]) {
this.fetchLogs(name);
attach: function (name) {
if (!name || !docker.client() || _streams[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] || [];
},
rename: function (name, newName) {
@ -63,5 +81,3 @@ var LogStore = assign(Object.create(EventEmitter.prototype), {
}
}
});
module.exports = LogStore;