aboutsummaryrefslogtreecommitdiff
path: root/frontend/src/components/Timeline.js
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/components/Timeline.js')
-rw-r--r--frontend/src/components/Timeline.js122
1 files changed, 87 insertions, 35 deletions
diff --git a/frontend/src/components/Timeline.js b/frontend/src/components/Timeline.js
index 6b8806f..bc42a01 100644
--- a/frontend/src/components/Timeline.js
+++ b/frontend/src/components/Timeline.js
@@ -35,8 +35,12 @@ import log from "../log";
import dispatcher from "../dispatcher";
const minutes = 60 * 1000;
+const _ = require('lodash');
const classNames = require('classnames');
+const leftSelectionPaddingMultiplier = 24;
+const rightSelectionPaddingMultiplier = 8;
+
class Timeline extends Component {
state = {
@@ -50,25 +54,30 @@ class Timeline extends Component {
this.selectionTimeout = null;
}
- filteredPort = () => {
+ additionalFilters = () => {
const urlParams = new URLSearchParams(this.props.location.search);
- return urlParams.get("service_port");
+ if (this.state.metric === "matched_rules") {
+ return urlParams.getAll("matched_rules") || [];
+ } else {
+ return urlParams.get("service_port");
+ }
};
componentDidMount() {
- const filteredPort = this.filteredPort();
- this.setState({filteredPort});
- this.loadStatistics(this.state.metric, filteredPort).then(() => log.debug("Statistics loaded after mount"));
+ const additionalFilters = this.additionalFilters();
+ this.setState({filters: additionalFilters});
+ this.loadStatistics(this.state.metric, additionalFilters).then(() => log.debug("Statistics loaded after mount"));
dispatcher.register("connection_updates", payload => {
this.setState({
selection: new TimeRange(payload.from, payload.to),
});
+ this.adjustSelection();
});
dispatcher.register("notifications", payload => {
if (payload.event === "services.edit") {
- this.loadServices().then(() => log.debug("Services reloaded after notification update"));
+ this.loadServices().then(() => this.adjustSelection());
}
});
@@ -79,27 +88,48 @@ class Timeline extends Component {
}
componentDidUpdate(prevProps, prevState, snapshot) {
- const filteredPort = this.filteredPort();
- if (this.state.filteredPort !== filteredPort) {
- this.setState({filteredPort});
- this.loadStatistics(this.state.metric, filteredPort).then(() =>
- log.debug("Statistics reloaded after filtered port changes"));
+ const additionalFilters = this.additionalFilters();
+ const updateStatistics = () => {
+ this.setState({filters: additionalFilters});
+ this.loadStatistics(this.state.metric, additionalFilters).then(() =>
+ log.debug("Statistics reloaded after filters changes"));
+ };
+
+ if (this.state.metric === "matched_rules") {
+ if (!Array.isArray(this.state.filters) ||
+ !_.isEqual(_.sortBy(additionalFilters), _.sortBy(this.state.filters))) {
+ updateStatistics();
+ }
+ } else {
+ if (this.state.filters !== additionalFilters) {
+ updateStatistics();
+ }
}
}
- loadStatistics = async (metric, filteredPort) => {
+ loadStatistics = async (metric, filters) => {
const urlParams = new URLSearchParams();
urlParams.set("metric", metric);
- let services = await this.loadServices();
- if (filteredPort && services[filteredPort]) {
- const service = services[filteredPort];
- services = {};
- services[filteredPort] = service;
- }
+ let columns = [];
+ if (metric === "matched_rules") {
+ let rules = await this.loadRules();
+ filters.forEach(id => {
+ urlParams.append("matched_rules", id);
+ });
+ columns = rules.map(r => r.id);
+ } else {
+ let services = await this.loadServices();
+ const filteredPort = filters;
+ if (filteredPort && services[filters]) {
+ const service = services[filteredPort];
+ services = {};
+ services[filteredPort] = service;
+ }
- const ports = Object.keys(services);
- ports.forEach(s => urlParams.append("ports", s));
+ columns = Object.keys(services);
+ columns.forEach(port => urlParams.append("ports", port));
+ }
const metrics = (await backend.get("/api/statistics?" + urlParams)).json;
if (metrics.length === 0) {
@@ -109,8 +139,8 @@ class Timeline extends Component {
const zeroFilledMetrics = [];
const toTime = m => new Date(m["range_start"]).getTime();
let i = 0;
- for (let interval = toTime(metrics[0]); interval <= toTime(metrics[metrics.length - 1]); interval += minutes) {
- if (interval === toTime(metrics[i])) {
+ for (let interval = toTime(metrics[0]) - minutes; interval <= toTime(metrics[metrics.length - 1]) + minutes; interval += minutes) {
+ if (i < metrics.length && interval === toTime(metrics[i])) {
const m = metrics[i++];
m["range_start"] = new Date(m["range_start"]);
zeroFilledMetrics.push(m);
@@ -118,30 +148,31 @@ class Timeline extends Component {
const m = {};
m["range_start"] = new Date(interval);
m[metric] = {};
- ports.forEach(p => m[metric][p] = 0);
+ columns.forEach(c => m[metric][c] = 0);
zeroFilledMetrics.push(m);
}
}
const series = new TimeSeries({
name: "statistics",
- columns: ["time"].concat(ports),
- points: zeroFilledMetrics.map(m => [m["range_start"]].concat(ports.map(p => m[metric][p] || 0)))
+ columns: ["time"].concat(columns),
+ points: zeroFilledMetrics.map(m => [m["range_start"]].concat(columns.map(c =>
+ ((metric in m) && (m[metric] != null)) ? (m[metric][c] || 0) : 0
+ )))
});
const start = series.range().begin();
const end = series.range().end();
- start.setTime(start.getTime() - minutes);
- end.setTime(end.getTime() + minutes);
this.setState({
metric,
series,
timeRange: new TimeRange(start, end),
+ columns,
start,
end
});
- log.debug(`Loaded statistics for metric "${metric}" for services [${ports}]`);
+ log.debug(`Loaded statistics for metric "${metric}"`);
};
loadServices = async () => {
@@ -150,10 +181,22 @@ class Timeline extends Component {
return services;
};
+ loadRules = async () => {
+ const rules = (await backend.get("/api/rules")).json;
+ this.setState({rules});
+ return rules;
+ };
+
createStyler = () => {
- return styler(Object.keys(this.state.services).map(port => {
- return {key: port, color: this.state.services[port].color, width: 2};
- }));
+ if (this.state.metric === "matched_rules") {
+ return styler(this.state.rules.map(rule => {
+ return {key: rule.id, color: rule.color, width: 2};
+ }));
+ } else {
+ return styler(Object.keys(this.state.services).map(port => {
+ return {key: port, color: this.state.services[port].color, width: 2};
+ }));
+ }
};
handleTimeRangeChange = (timeRange) => {
@@ -179,6 +222,15 @@ class Timeline extends Component {
}, 1000);
};
+ adjustSelection = () => {
+ const seriesRange = this.state.series.range();
+ const selection = this.state.selection;
+ const delta = selection.end() - selection.begin();
+ const start = Math.max(selection.begin().getTime() - delta * leftSelectionPaddingMultiplier, seriesRange.begin().getTime());
+ const end = Math.min(selection.end().getTime() + delta * rightSelectionPaddingMultiplier, seriesRange.end().getTime());
+ this.setState({timeRange: new TimeRange(start, end)});
+ };
+
aggregateSeries = (func) => {
const values = this.state.series.columns().map(c => this.state.series[func](c));
return Math[func](...values);
@@ -207,7 +259,7 @@ class Timeline extends Component {
max={this.aggregateSeries("max")} width="35" type="linear" transition={300}/>
<Charts>
<LineChart axis="axis1" series={this.state.series}
- columns={Object.keys(this.state.services)}
+ columns={this.state.columns}
style={this.createStyler()} interpolation="curveBasis"/>
<MultiBrush
@@ -224,10 +276,10 @@ class Timeline extends Component {
<div className="metric-selection">
<ChoiceField inline small
keys={["connections_per_service", "client_bytes_per_service",
- "server_bytes_per_service", "duration_per_service"]}
+ "server_bytes_per_service", "duration_per_service", "matched_rules"]}
values={["connections_per_service", "client_bytes_per_service",
- "server_bytes_per_service", "duration_per_service"]}
- onChange={(metric) => this.loadStatistics(metric, this.state.filteredPort)
+ "server_bytes_per_service", "duration_per_service", "matched_rules"]}
+ onChange={(metric) => this.loadStatistics(metric, this.state.filters)
.then(() => log.debug("Statistics loaded after metric changes"))}
value={this.state.metric}/>
</div>