diff --git a/README.md b/README.md index cd23272083..72996ff18c 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/src/ContainerHome.react.js b/src/ContainerHome.react.js index 530e1b2668..a25fa678b5 100644 --- a/src/ContainerHome.react.js +++ b/src/ContainerHome.react.js @@ -112,7 +112,7 @@ var ContainerHome = React.createClass({
- +
@@ -138,7 +138,7 @@ var ContainerHome = React.createClass({
- +
{right}
diff --git a/src/ContainerHomeLogs.react.js b/src/ContainerHomeLogs.react.js index d400a48d72..4a471202ff 100644 --- a/src/ContainerHomeLogs.react.js +++ b/src/ContainerHomeLogs.react.js @@ -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

; + return ; }); if (logs.length === 0) { logs = "No logs for this container."; @@ -62,11 +61,8 @@ var ContainerHomeLogs = React.createClass({
{logs}
-
View Logs
-
+
View Logs
); } }); - -module.exports = ContainerHomeLogs; diff --git a/src/ContainerLogs.react.js b/src/ContainerLogs.react.js index e20910cc49..f7b0b24064 100644 --- a/src/ContainerLogs.react.js +++ b/src/ContainerLogs.react.js @@ -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

; + return ; }); if (logs.length === 0) { logs = "No logs for this container."; @@ -55,5 +52,3 @@ var ContainerLogs = React.createClass({ ); } }); - -module.exports = ContainerLogs; diff --git a/src/LogStore.js b/src/LogStore.js index 9735b5b98c..a42010131b 100644 --- a/src/LogStore.js +++ b/src/LogStore.js @@ -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;