singularityui-tailer
Version:
A robust log tailer
379 lines (314 loc) • 13.3 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _reactDom = require('react-dom');
var _reactDom2 = _interopRequireDefault(_reactDom);
var _reactRedux = require('react-redux');
var _classnames = require('classnames');
var _classnames2 = _interopRequireDefault(_classnames);
var _immutable = require('immutable');
var _immutable2 = _interopRequireDefault(_immutable);
var _connectToTailer = require('./connectToTailer');
var _connectToTailer2 = _interopRequireDefault(_connectToTailer);
var _selectors = require('../selectors');
var Selectors = _interopRequireWildcard(_selectors);
var _LogLines = require('./LogLines');
var _LogLines2 = _interopRequireDefault(_LogLines);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var SCROLL_LOAD_THRESHOLD = 300;
var Log = function (_Component) {
_inherits(Log, _Component);
function Log() {
_classCallCheck(this, Log);
var _this = _possibleConstructorReturn(this, (Log.__proto__ || Object.getPrototypeOf(Log)).call(this));
_this.isLineLoaded = _this.isLineLoaded.bind(_this);
_this.loadLine = _this.loadLine.bind(_this);
_this.tailLog = _this.tailLog.bind(_this);
_this.pollScroll = _this.pollScroll.bind(_this);
_this.handleTail = _this.handleTail.bind(_this);
_this.getTailDelayMs = _this.getTailDelayMs.bind(_this);
_this.scheduleNextHandleTail = _this.scheduleNextHandleTail.bind(_this);
_this.scrollTop = undefined;
_this.scrollHeight = undefined;
_this.invalidate = false;
_this.fakeLineCount = 0;
_this.scrollDelta = 0;
_this.tailAttempt = 0;
return _this;
}
_createClass(Log, [{
key: 'componentWillMount',
value: function componentWillMount() {
if (this.props.goToOffset === -1 && !this.props.tailing) {
this.props.startTailing();
}
}
}, {
key: 'componentDidMount',
value: function componentDidMount() {
if (!this.props.isLoaded) {
this.props.initializeFile(this.props.goToOffset);
} else {
this.tailLog();
}
this.rafRequestId = window.requestAnimationFrame(this.pollScroll);
}
}, {
key: 'componentWillUpdate',
value: function componentWillUpdate(nextProps, nextState) {
if (nextProps.lines !== this.props.lines) {
this.invalidate = true;
if (this.props.tailing) {
this.scrollDelta = -1;
}
}
if (nextProps.lines.size > 1 && this.props.lines.size > 1) {
var oldLines = this.props.lines;
var newLines = nextProps.lines;
var addedToBeginning = newLines.findIndex(function (l) {
return l.start >= oldLines.get(0).end;
}) - 1;
var removedFromBeginning = oldLines.findIndex(function (l) {
return l.end >= newLines.get(0).end;
});
if (removedFromBeginning) {
this.fakeLineCount += removedFromBeginning - 1;
}
if (addedToBeginning) {
if (this.fakeLineCount - addedToBeginning >= 0) {
this.fakeLineCount -= addedToBeginning;
} else {
this.scrollDelta += _LogLines.LOG_LINE_HEIGHT * (addedToBeginning - this.fakeLineCount);
this.fakeLineCount = 0;
}
}
}
if (nextProps.tailing && this.tailTimeoutId == null) {
this.handleTail();
} else if (!nextProps.tailing && this.tailTimeoutId != null) {
clearTimeout(this.tailTimeoutId);
this.tailTimeoutId = null;
}
}
}, {
key: 'getTailDelayMs',
value: function getTailDelayMs() {
return Math.min(this.props.maxTailDelayMs, Math.max(this.props.minTailDelayMs, this.props.calculateTailDelayMs(this.tailAttempt)));
}
}, {
key: 'scheduleNextHandleTail',
value: function scheduleNextHandleTail() {
if (this.props.tailing) {
this.tailTimeoutId = setTimeout(this.handleTail, this.getTailDelayMs());
}
}
}, {
key: 'handleTail',
value: function handleTail() {
var _this2 = this;
if (this.props.lines.size > 1) {
this.loadLine(this.props.lines.size - 1, false).then(function (data) {
if (data && data.hasOwnProperty('chunk')) {
if (data.chunk.start === data.chunk.end) {
_this2.tailAttempt++;
} else {
_this2.tailAttempt = 0;
}
}
_this2.scheduleNextHandleTail();
});
} else {
this.scheduleNextHandleTail();
}
}
}, {
key: 'componentDidUpdate',
value: function componentDidUpdate(prevProps) {
var idMatches = this.props.tailerId === prevProps.tailerId;
var offsetMatches = this.props.goToOffset === prevProps.goToOffset;
if (!idMatches || !offsetMatches) {
this.props.initializeFile(this.props.goToOffset);
}
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
window.cancelAnimationFrame(this.rafRequestId);
clearInterval(this.tailTimeoutId);
}
}, {
key: 'isLineLoaded',
value: function isLineLoaded(index) {
return index < this.props.lines.size && !this.props.lines.get(index).isMissingMarker && !this.props.requests.has(this.props.lines.get(index).start);
}
// detect when dom has changed underneath us- either scrollTop or scrollHeight (layout reflow)
// may have changed.
}, {
key: 'pollScroll',
value: function pollScroll() {
var _this3 = this;
var domNode = _reactDom2.default.findDOMNode(this);
// let's update the scroll now, in a raf.
if (this.scrollDelta > 0) {
domNode.scrollTop += this.scrollDelta;
this.scrollDelta = 0;
} else if (this.scrollDelta === -1) {
domNode.scrollTop = domNode.scrollHeight;
this.scrollDelta = 0;
}
var scrollTop = domNode.scrollTop,
scrollHeight = domNode.scrollHeight,
clientHeight = domNode.clientHeight;
if (scrollTop !== this.scrollTop || this.invalidate) {
this.invalidate = false;
var scrollLoadThreshold = _LogLines.LOG_LINE_HEIGHT * SCROLL_LOAD_THRESHOLD;
var nearTop = scrollTop - this.fakeLineCount * _LogLines.LOG_LINE_HEIGHT <= scrollLoadThreshold;
var nearBottom = scrollTop >= scrollHeight - clientHeight - scrollLoadThreshold;
var atBottom = scrollTop >= scrollHeight - clientHeight - 1;
var lines = this.props.lines;
if (nearTop && nearBottom && lines.size === 1) {
// wait until the first chunk is loaded.
} else {
// we are at the top/bottom, load some stuff.
if (nearTop && !this.isLineLoaded(0)) {
// don't dispatch in the raf, do it later
setTimeout(function () {
return _this3.loadLine(0, true);
}, 0);
}
if (nearBottom && lines.size) {
// if we haven't reached the end of the file yet
if (lines.last().isMissingMarker) {
// don't dispatch in the raf, do it later
setTimeout(function () {
return _this3.loadLine(lines.size - 1, false);
}, 0);
} else if (atBottom) {
// we're tailing here.
if (!this.props.tailing) {
this.props.startTailing();
}
}
}
}
if (!atBottom && this.props.tailing) {
this.props.stopTailing();
}
// update the scroll position.
this.scrollTop = domNode.scrollTop;
this.scrollHeight = domNode.scrollHeight;
}
// do another raf.
this.rafRequestId = window.requestAnimationFrame(this.pollScroll);
}
}, {
key: 'loadLine',
value: function loadLine(index, loadUp) {
return this.props.loadLine(index, loadUp, this.props.lines, this.props.chunks);
}
}, {
key: 'tailLog',
value: function tailLog() {
return this.props.tailLog(this.props.lines);
}
}, {
key: 'render',
value: function render() {
var props = this.props;
var hrefFunc = props.hrefFunc ? function (offset) {
return props.hrefFunc(props.tailerId, offset);
} : undefined;
var logPaneClasses = (0, _classnames2.default)({
'log-pane': true,
tailing: this.props.tailing
});
return _react2.default.createElement(
'section',
{ className: logPaneClasses },
_react2.default.createElement(
'div',
{ className: 'log-line-wrapper' },
_react2.default.createElement(_LogLines2.default, {
isLoaded: props.isLoaded,
lines: props.lines,
fakeLineCount: this.fakeLineCount,
isLineLoaded: this.isLineLoaded,
hrefFunc: hrefFunc,
lineLinkRenderer: props.lineLinkRenderer,
highlightedOffset: props.goToOffset
})
)
);
}
}]);
return Log;
}(_react.Component);
Log.propTypes = {
tailerId: _react.PropTypes.string.isRequired,
goToOffset: _react.PropTypes.number,
hrefFunc: _react.PropTypes.func,
minTailDelayMs: _react.PropTypes.number.isRequired,
maxTailDelayMs: _react.PropTypes.number.isRequired,
calculateTailDelayMs: _react.PropTypes.func.isRequired,
// from connectToTailer HOC
getTailerState: _react.PropTypes.func.isRequired,
// from tailer implementation
// actions
initializeFile: _react.PropTypes.func.isRequired,
loadLine: _react.PropTypes.func.isRequired,
tailLog: _react.PropTypes.func.isRequired,
// from connect
isLoaded: _react.PropTypes.bool.isRequired,
fileSize: _react.PropTypes.number,
lines: _react.PropTypes.instanceOf(_immutable2.default.List),
chunks: _react.PropTypes.instanceOf(_immutable2.default.List),
requests: _react.PropTypes.instanceOf(_immutable2.default.Map),
config: _react.PropTypes.object.isRequired,
lineLinkRenderer: _react.PropTypes.func,
startTailing: _react.PropTypes.func.isRequired,
stopTailing: _react.PropTypes.func.isRequired,
tailing: _react.PropTypes.bool
};
Log.defaultProps = {
minTailDelayMs: 100,
maxTailDelayMs: 5000,
calculateTailDelayMs: function calculateTailDelayMs(attempt) {
return 100 * attempt;
}
};
var makeMapStateToProps = function makeMapStateToProps() {
var getEnhancedLines = Selectors.makeGetEnhancedLines();
var mapStateToProps = function mapStateToProps(state, ownProps) {
return {
isLoaded: Selectors.getIsLoaded(state, ownProps),
fileSize: Selectors.getFileSize(state, ownProps),
lines: getEnhancedLines(state, ownProps),
chunks: Selectors.getChunks(state, ownProps),
requests: Selectors.getRequests(state, ownProps),
config: Selectors.getConfig(state, ownProps),
tailing: Selectors.isTailing(state, ownProps)
};
};
return mapStateToProps;
};
var _default = (0, _connectToTailer2.default)((0, _reactRedux.connect)(makeMapStateToProps)(Log));
exports.default = _default;
;
var _temp = function () {
if (typeof __REACT_HOT_LOADER__ === 'undefined') {
return;
}
__REACT_HOT_LOADER__.register(SCROLL_LOAD_THRESHOLD, 'SCROLL_LOAD_THRESHOLD', 'src/components/Log.js');
__REACT_HOT_LOADER__.register(Log, 'Log', 'src/components/Log.js');
__REACT_HOT_LOADER__.register(makeMapStateToProps, 'makeMapStateToProps', 'src/components/Log.js');
__REACT_HOT_LOADER__.register(_default, 'default', 'src/components/Log.js');
}();
;