diff options
author | Emiliano Ciavatta | 2020-10-15 06:53:09 +0000 |
---|---|---|
committer | Emiliano Ciavatta | 2020-10-15 06:53:09 +0000 |
commit | 08456e7f2e1c1af6fc8fdbf580c0178a25b93f8b (patch) | |
tree | 5c98b7c4b33848dfe92c22b9902f228a90fdacbf /frontend/src/components/Timeline.js | |
parent | 09cb0a1518feb2221ccd8c10dced859c010e9991 (diff) |
General improvements
Diffstat (limited to 'frontend/src/components/Timeline.js')
-rw-r--r-- | frontend/src/components/Timeline.js | 122 |
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> |