@@ -0,0 +1,140 @@
/* global jsRoutes */

'use strict';

var React = require('react');
var ProgressBar = require('react-bootstrap').ProgressBar;
var Row = require('react-bootstrap').Row;
var Col = require('react-bootstrap').Col;
var Button = require('react-bootstrap').Button;
//noinspection JSUnusedGlobalSymbols
var MetricsStore = require('../../stores/metrics/MetricsStore');
var numeral = require('numeral');
var moment = require('moment');
require('moment-duration-format');

var metricsStore = MetricsStore.instance;

var JournalDetails = React.createClass({
getInitialState() {
return ({
initialized: false,
hasError: false,
append: 0,
read: 0,
segments: 0,
entriesUncommitted: 0,
size: 0,
sizeLimit: 0,
utilizationRatio: 0,
oldestSegment: moment()
});
},

componentWillMount() {
// only listen for updates if the journal is actually turned on
if (this.props.enabled) {
var metricNames = [
"org.graylog2.journal.append.1-sec-rate",
"org.graylog2.journal.read.1-sec-rate",
"org.graylog2.journal.segments",
"org.graylog2.journal.entries-uncommitted",
"org.graylog2.journal.size",
"org.graylog2.journal.size-limit",
"org.graylog2.journal.utilization-ratio",
"org.graylog2.journal.oldest-segment"
];
metricsStore.listen({
nodeId: this.props.nodeId,
metricNames: metricNames,
callback: (update, hasError) => {
if (hasError) {
this.setState({hasError: hasError});
}
// update is [{nodeId, values: [{name, value: {metric}}]} ...]
// metric can be various different things, depending on metric {type: "GAUGE"|"COUNTER"|"METER"|"TIMER"}

var newState = {
initialized: true,
hasError: hasError
};
// we will only get one result, because we ask for only one node
// get the base name from the metric name, and put the gauge metrics into our new state.
update[0].values.forEach((namedMetric) => {
var baseName = namedMetric.name.replace('org.graylog2.journal.', '').replace('.1-sec-rate', '');
var camelCase = this.camelCase(baseName);
newState[camelCase] = namedMetric.metric.value;
});
this.setState(newState);
}
});
}
},

render() {
var content = null;
if (this.props.enabled) {
var oldestSegment = moment(this.state.oldestSegment);
var overcommittedWarning = null;
if (this.state.initialized && (this.state.utilizationRatio >= 1)) {
overcommittedWarning = <span>
<strong>Warning!</strong> The journal utilization is exceeding the maximum size defined.
<a href={jsRoutes.controllers.SystemController.index().url}> Click here</a> for more information.<br/>
</span>;
}

content = (
<div>
<p>Incoming messages are written to the disk journal to ensure they are kept safe in case of a server failure. The journal also helps keeping Graylog working if any of the outputs is too slow to keep up with the message rate
or whenever there is a peak in incoming messages. It makes sure that Graylog does not buffer all of those messages in main memory and avoids overly long garbage collection pauses that way.
</p>
<Row>
<Col md={6}>
<h3>Configuration</h3>
<dl className="system-journal">
<dt>Path:</dt>
<dd>{this.props.directory}</dd>
<dt>Earliest entry: </dt>
<dd><time dateTime={oldestSegment} title={oldestSegment.toISOString()}>{oldestSegment.fromNow()}</time></dd>
<dt>Maximum size:</dt>
<dd>{numeral(this.props.maxSize).format('0,0 b')}</dd>
<dt>Maximum age:</dt>
<dd>{moment.duration(this.props.maxAge).format("d [days] h [hours] m [minutes]")}</dd>
<dt>Flush policy:</dt>
<dd>
Every {numeral(this.props.flushInterval).format('0,0')} messages or {moment.duration(this.props.flushAge).format("h [hours] m [minutes] s [seconds]")}
</dd>
</dl>
</Col>
<Col md={6}>
<h3>Utilization</h3>

<ProgressBar now={this.state.utilizationRatio * 100} label={numeral(this.state.utilizationRatio).format("0.0 %")} />

{overcommittedWarning}

<strong>{numeral(this.state.entriesUncommitted).format('0,0')} unprocessed messages</strong> are currently in the journal, in {this.state.segments} segments.<br/>
<strong>{numeral(this.state.append).format('0,0')} messages</strong> have been appended in the last second,
<strong>{numeral(this.state.read).format('0,0')} messages</strong> have been read in the last second.
</Col>
</Row>
</div>
);
} else {
content = (<span>The disk journal is disabled for this node.</span>);
}

return (<div>
<h2><i className="fa fa-hdd-o"></i> Disk Journal</h2>
{content}
</div>);

},
camelCase(input) {
return input.toLowerCase().replace(/-(.)/g, function(match, group1) {
return group1.toUpperCase();
});
}
});

module.exports = JournalDetails;
@@ -3,6 +3,7 @@
var React = require('react/addons');
var JvmHeapUsage = require('./JvmHeapUsage');
var BufferUsage = require('./BufferUsage');
var JournalDetails = require('./JournalDetails');

var heapUsage = document.getElementsByClassName('react-jvm-heap');
if (heapUsage) {
@@ -22,4 +23,18 @@ if (buffers) {
var bufferType = elem.getAttribute('data-buffer-type');
React.render(<BufferUsage nodeId={id} title={title} bufferType={bufferType}/>, elem);
}
}

var journal = document.getElementById('react-journal-info');
if (journal) {
var id = elem.getAttribute('data-node-id');
var dir = journal.getAttribute('data-journal-dir');
var enabled = journal.getAttribute('data-journal-enabled');
var maxSize = journal.getAttribute('data-journal-max-size');
var maxAge = journal.getAttribute('data-journal-maxage');
var flushInterval = journal.getAttribute('data-journal-flush-interval');
var flushAge = journal.getAttribute('data-journal-flush-age');

React.render(<JournalDetails nodeId={id} directory={dir} enabled={enabled} maxSize={maxSize} maxAge={maxAge}
flushInterval={flushInterval} flushAge={flushAge}/>, journal);
}
@@ -19,6 +19,7 @@ var SourcePieChart = require('./SourcePieChart');
var SourceLineChart = require('./SourceLineChart');

var UniversalSearch = require('../../logic/search/UniversalSearch');
var moment = require('moment')

var daysToSeconds = (days) => moment.duration(days, 'days').as('seconds');
var hoursToSeconds = (hours) => moment.duration(hours, 'hours').as('seconds');