From d203f3c7e3bcaa20895c0f32f348cd1513ae9876 Mon Sep 17 00:00:00 2001 From: Emiliano Ciavatta Date: Thu, 8 Oct 2020 22:17:04 +0200 Subject: Frontend folder structure refactor --- .../components/objects/ConnectionMatchedRules.js | 46 ++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 frontend/src/components/objects/ConnectionMatchedRules.js (limited to 'frontend/src/components/objects/ConnectionMatchedRules.js') diff --git a/frontend/src/components/objects/ConnectionMatchedRules.js b/frontend/src/components/objects/ConnectionMatchedRules.js new file mode 100644 index 0000000..73d5c5d --- /dev/null +++ b/frontend/src/components/objects/ConnectionMatchedRules.js @@ -0,0 +1,46 @@ +/* + * This file is part of caronte (https://github.com/eciavatta/caronte). + * Copyright (c) 2020 Emiliano Ciavatta. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import React, {Component} from 'react'; +import './ConnectionMatchedRules.scss'; +import ButtonField from "../fields/ButtonField"; + +class ConnectionMatchedRules extends Component { + + constructor(props) { + super(props); + this.state = { + }; + } + + render() { + const matchedRules = this.props.matchedRules.map(mr => { + const rule = this.props.rules.find(r => r.id === mr); + return this.props.addMatchedRulesFilter(rule.id)} name={rule.name} + color={rule.color} small />; + }); + + return ( + + matched_rules: + {matchedRules} + + ); + } +} + +export default ConnectionMatchedRules; -- cgit v1.2.3-70-g09d2 From 08456e7f2e1c1af6fc8fdbf580c0178a25b93f8b Mon Sep 17 00:00:00 2001 From: Emiliano Ciavatta Date: Thu, 15 Oct 2020 08:53:09 +0200 Subject: General improvements --- frontend/public/logo128.png | Bin 0 -> 6498 bytes frontend/public/logo192.png | Bin 6498 -> 0 bytes frontend/public/manifest.json | 8 +- frontend/src/components/Header.js | 2 +- frontend/src/components/Timeline.js | 122 +++++++++++++++------ frontend/src/components/Timeline.scss | 1 + .../components/filters/BooleanConnectionsFilter.js | 64 ++++------- .../src/components/filters/ExitSearchFilter.js | 9 +- .../src/components/filters/FiltersDispatcher.js | 57 ---------- .../components/filters/RulesConnectionsFilter.js | 81 ++++++-------- .../components/filters/StringConnectionsFilter.js | 77 ++++++------- frontend/src/components/objects/Connection.js | 4 +- .../components/objects/ConnectionMatchedRules.js | 21 ++-- frontend/src/components/pages/MainPage.js | 3 - frontend/src/components/panels/ConnectionsPane.js | 122 ++++++++++++--------- frontend/src/components/panels/SearchPane.scss | 1 + frontend/src/components/panels/StreamsPane.js | 2 +- frontend/src/dispatcher.js | 6 + search_controller.go | 4 + statistics_controller.go | 19 ++-- 20 files changed, 299 insertions(+), 304 deletions(-) create mode 100644 frontend/public/logo128.png delete mode 100644 frontend/public/logo192.png delete mode 100644 frontend/src/components/filters/FiltersDispatcher.js (limited to 'frontend/src/components/objects/ConnectionMatchedRules.js') diff --git a/frontend/public/logo128.png b/frontend/public/logo128.png new file mode 100644 index 0000000..1969e1d Binary files /dev/null and b/frontend/public/logo128.png differ diff --git a/frontend/public/logo192.png b/frontend/public/logo192.png deleted file mode 100644 index 1969e1d..0000000 Binary files a/frontend/public/logo192.png and /dev/null differ diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json index 0409a59..32674ce 100644 --- a/frontend/public/manifest.json +++ b/frontend/public/manifest.json @@ -1,6 +1,6 @@ { - "short_name": "Caronte", - "name": "Caronte", + "short_name": "caronte", + "name": "caronte", "icons": [ { "src": "favicon.ico", @@ -8,9 +8,9 @@ "type": "image/x-icon" }, { - "src": "logo192.png", + "src": "logo128.png", "type": "image/png", - "sizes": "192x192" + "sizes": "128x128" }, { "src": "logo512.png", diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js index b72b532..b4a2177 100644 --- a/frontend/src/components/Header.js +++ b/frontend/src/components/Header.js @@ -89,7 +89,7 @@ class Header extends Component {
- {/**/} + 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}/> 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}/>
diff --git a/frontend/src/components/Timeline.scss b/frontend/src/components/Timeline.scss index db8d9c8..262da1e 100644 --- a/frontend/src/components/Timeline.scss +++ b/frontend/src/components/Timeline.scss @@ -12,6 +12,7 @@ position: absolute; top: 5px; right: 10px; + width: 180px; } &.pulse-timeline { diff --git a/frontend/src/components/filters/BooleanConnectionsFilter.js b/frontend/src/components/filters/BooleanConnectionsFilter.js index a9a420e..c611a0d 100644 --- a/frontend/src/components/filters/BooleanConnectionsFilter.js +++ b/frontend/src/components/filters/BooleanConnectionsFilter.js @@ -17,65 +17,49 @@ import React, {Component} from 'react'; import {withRouter} from "react-router-dom"; -import {Redirect} from "react-router"; import CheckField from "../fields/CheckField"; +import dispatcher from "../../dispatcher"; class BooleanConnectionsFilter extends Component { - constructor(props) { - super(props); - this.state = { - filterActive: "false" - }; - - this.filterChanged = this.filterChanged.bind(this); - this.needRedirect = false; - } + state = { + filterActive: "false" + }; componentDidMount() { let params = new URLSearchParams(this.props.location.search); this.setState({filterActive: this.toBoolean(params.get(this.props.filterName)).toString()}); + + this.connectionsFiltersCallback = payload => { + const name = this.props.filterName; + if (name in payload && this.state.filterActive !== payload[name]) { + this.setState({filterActive: payload[name]}); + } + }; + dispatcher.register("connections_filters", this.connectionsFiltersCallback); } - componentDidUpdate(prevProps, prevState, snapshot) { - let urlParams = new URLSearchParams(this.props.location.search); - let externalActive = this.toBoolean(urlParams.get(this.props.filterName)); - let filterActive = this.toBoolean(this.state.filterActive); - // if the filterActive state is changed by another component (and not by filterChanged func) and - // the query string is not equals at the filterActive state, update the state of the component - if (this.toBoolean(prevState.filterActive) === filterActive && filterActive !== externalActive) { - this.setState({filterActive: externalActive.toString()}); - } + componentWillUnmount() { + dispatcher.unregister(this.connectionsFiltersCallback); } - toBoolean(value) { + toBoolean = (value) => { return value !== null && value.toLowerCase() === "true"; - } + }; - filterChanged() { - this.needRedirect = true; - this.setState({filterActive: (!this.toBoolean(this.state.filterActive)).toString()}); - } + filterChanged = () => { + const newValue = (!this.toBoolean(this.state.filterActive)).toString(); + const urlParams = {}; + urlParams[this.props.filterName] = newValue === "true" ? "true" : null; + dispatcher.dispatch("connections_filters", urlParams); + this.setState({filterActive: newValue}); + }; render() { - let redirect = null; - if (this.needRedirect) { - let urlParams = new URLSearchParams(this.props.location.search); - if (this.toBoolean(this.state.filterActive)) { - urlParams.set(this.props.filterName, "true"); - } else { - urlParams.delete(this.props.filterName); - } - redirect = ; - - this.needRedirect = false; - } - return (
- {redirect} + onChange={this.filterChanged}/>
); } diff --git a/frontend/src/components/filters/ExitSearchFilter.js b/frontend/src/components/filters/ExitSearchFilter.js index cfee298..68ca686 100644 --- a/frontend/src/components/filters/ExitSearchFilter.js +++ b/frontend/src/components/filters/ExitSearchFilter.js @@ -28,11 +28,16 @@ class ExitSearchFilter extends Component { let params = new URLSearchParams(this.props.location.search); this.setState({performedSearch: params.get("performed_search")}); - dispatcher.register("connections_filters", payload => { + this.connectionsFiltersCallback = payload => { if (this.state.performedSearch !== payload["performed_search"]) { this.setState({performedSearch: payload["performed_search"]}); } - }); + }; + dispatcher.register("connections_filters", this.connectionsFiltersCallback); + } + + componentWillUnmount() { + dispatcher.unregister(this.connectionsFiltersCallback); } render() { diff --git a/frontend/src/components/filters/FiltersDispatcher.js b/frontend/src/components/filters/FiltersDispatcher.js deleted file mode 100644 index 3769055..0000000 --- a/frontend/src/components/filters/FiltersDispatcher.js +++ /dev/null @@ -1,57 +0,0 @@ -/* - * This file is part of caronte (https://github.com/eciavatta/caronte). - * Copyright (c) 2020 Emiliano Ciavatta. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import React, {Component} from 'react'; -import {withRouter} from "react-router-dom"; -import {Redirect} from "react-router"; -import dispatcher from "../../dispatcher"; - -class FiltersDispatcher extends Component { - - state = {}; - - componentDidMount() { - let params = new URLSearchParams(this.props.location.search); - this.setState({params}); - - dispatcher.register("connections_filters", payload => { - const params = this.state.params; - - Object.entries(payload).forEach(([key, value]) => { - if (value == null) { - params.delete(key); - } else { - params.set(key, value); - } - }); - - this.needRedirect = true; - this.setState({params}); - }); - } - - render() { - if (this.needRedirect) { - this.needRedirect = false; - return ; - } - - return null; - } -} - -export default withRouter(FiltersDispatcher); diff --git a/frontend/src/components/filters/RulesConnectionsFilter.js b/frontend/src/components/filters/RulesConnectionsFilter.js index fc0ad4d..4c993dc 100644 --- a/frontend/src/components/filters/RulesConnectionsFilter.js +++ b/frontend/src/components/filters/RulesConnectionsFilter.js @@ -17,87 +17,74 @@ import React, {Component} from 'react'; import {withRouter} from "react-router-dom"; -import {Redirect} from "react-router"; import './RulesConnectionsFilter.scss'; import ReactTags from 'react-tag-autocomplete'; import backend from "../../backend"; +import dispatcher from "../../dispatcher"; const classNames = require('classnames'); +const _ = require('lodash'); class RulesConnectionsFilter extends Component { - constructor(props) { - super(props); - this.state = { - mounted: false, - rules: [], - activeRules: [] - }; - - this.needRedirect = false; - } + state = { + rules: [], + activeRules: [] + }; componentDidMount() { - let params = new URLSearchParams(this.props.location.search); + const params = new URLSearchParams(this.props.location.search); let activeRules = params.getAll("matched_rules") || []; backend.get("/api/rules").then(res => { let rules = res.json.flatMap(rule => rule.enabled ? [{id: rule.id, name: rule.name}] : []); activeRules = rules.filter(rule => activeRules.some(id => rule.id === id)); - this.setState({rules, activeRules, mounted: true}); + this.setState({rules, activeRules}); }); + + this.connectionsFiltersCallback = payload => { + if ("matched_rules" in payload && !_.isEqual(payload["matched_rules"].sort(), this.state.activeRules.sort())) { + const newRules = this.state.rules.filter(r => payload["matched_rules"].includes(r.id)); + this.setState({ + activeRules: newRules.map(r => { + return {id: r.id, name: r.name}; + }) + }); + } + }; + dispatcher.register("connections_filters", this.connectionsFiltersCallback); } - componentDidUpdate(prevProps, prevState, snapshot) { - let urlParams = new URLSearchParams(this.props.location.search); - let externalRules = urlParams.getAll("matched_rules") || []; - let activeRules = this.state.activeRules.map(r => r.id); - let compareRules = (first, second) => first.sort().join(",") === second.sort().join(","); - if (this.state.mounted && - compareRules(prevState.activeRules.map(r => r.id), activeRules) && - !compareRules(externalRules, activeRules)) { - this.setState({activeRules: externalRules.map(id => this.state.rules.find(r => r.id === id))}); - } + componentWillUnmount() { + dispatcher.unregister(this.connectionsFiltersCallback); } - onDelete(i) { - const activeRules = this.state.activeRules.slice(0); + onDelete = (i) => { + const activeRules = _.clone(this.state.activeRules); activeRules.splice(i, 1); - this.needRedirect = true; - this.setState({ activeRules }); - } + this.setState({activeRules}); + dispatcher.dispatch("connections_filters", {"matched_rules": activeRules.map(r => r.id)}); + }; - onAddition(rule) { + onAddition = (rule) => { if (!this.state.activeRules.includes(rule)) { const activeRules = [].concat(this.state.activeRules, rule); - this.needRedirect = true; this.setState({activeRules}); + dispatcher.dispatch("connections_filters", {"matched_rules": activeRules.map(r => r.id)}); } - } + }; render() { - let redirect = null; - - if (this.needRedirect) { - let urlParams = new URLSearchParams(this.props.location.search); - urlParams.delete("matched_rules"); - this.state.activeRules.forEach(rule => urlParams.append("matched_rules", rule.id)); - redirect = ; - - this.needRedirect = false; - } - return ( -
+
- suggestion.name.startsWith(query) && !this.state.activeRules.includes(suggestion)} /> + suggestion.name.startsWith(query) && !this.state.activeRules.includes(suggestion)}/>
- - {redirect}
); } diff --git a/frontend/src/components/filters/StringConnectionsFilter.js b/frontend/src/components/filters/StringConnectionsFilter.js index a3b45dc..c833220 100644 --- a/frontend/src/components/filters/StringConnectionsFilter.js +++ b/frontend/src/components/filters/StringConnectionsFilter.js @@ -17,37 +17,36 @@ import React, {Component} from 'react'; import {withRouter} from "react-router-dom"; -import {Redirect} from "react-router"; import InputField from "../fields/InputField"; +import dispatcher from "../../dispatcher"; class StringConnectionsFilter extends Component { - constructor(props) { - super(props); - this.state = { - fieldValue: "", - filterValue: null, - timeoutHandle: null, - invalidValue: false - }; - this.needRedirect = false; - this.filterChanged = this.filterChanged.bind(this); - } + state = { + fieldValue: "", + filterValue: null, + timeoutHandle: null, + invalidValue: false + }; componentDidMount() { let params = new URLSearchParams(this.props.location.search); this.updateStateFromFilterValue(params.get(this.props.filterName)); + + this.connectionsFiltersCallback = payload => { + const name = this.props.filterName; + if (name in payload && this.state.filterValue !== payload[name]) { + this.updateStateFromFilterValue(payload[name]); + } + }; + dispatcher.register("connections_filters", this.connectionsFiltersCallback); } - componentDidUpdate(prevProps, prevState, snapshot) { - let urlParams = new URLSearchParams(this.props.location.search); - let filterValue = urlParams.get(this.props.filterName); - if (prevState.filterValue === this.state.filterValue && this.state.filterValue !== filterValue) { - this.updateStateFromFilterValue(filterValue); - } + componentWillUnmount() { + dispatcher.unregister(this.connectionsFiltersCallback); } - updateStateFromFilterValue(filterValue) { + updateStateFromFilterValue = (filterValue) => { if (filterValue !== null) { let fieldValue = filterValue; if (typeof this.props.decodeFunc === "function") { @@ -70,15 +69,21 @@ class StringConnectionsFilter extends Component { } else { this.setState({fieldValue: "", filterValue: null}); } - } + }; - isValueValid(value) { + isValueValid = (value) => { return typeof this.props.validateFunc !== "function" || (typeof this.props.validateFunc === "function" && this.props.validateFunc(value)); - } + }; - filterChanged(fieldValue) { - if (this.state.timeoutHandle !== null) { + changeFilterValue = (value) => { + const urlParams = {}; + urlParams[this.props.filterName] = value; + dispatcher.dispatch("connections_filters", urlParams); + }; + + filterChanged = (fieldValue) => { + if (this.state.timeoutHandle) { clearTimeout(this.state.timeoutHandle); } @@ -87,11 +92,12 @@ class StringConnectionsFilter extends Component { } if (fieldValue === "") { - this.needRedirect = true; this.setState({fieldValue: "", filterValue: null, invalidValue: false}); - return; + return this.changeFilterValue(null); } + + if (this.isValueValid(fieldValue)) { let filterValue = fieldValue; if (filterValue !== "" && typeof this.props.encodeFunc === "function") { @@ -101,40 +107,27 @@ class StringConnectionsFilter extends Component { this.setState({ fieldValue: fieldValue, timeoutHandle: setTimeout(() => { - this.needRedirect = true; this.setState({filterValue: filterValue}); + this.changeFilterValue(filterValue); }, 500), invalidValue: false }); } else { - this.needRedirect = true; this.setState({ fieldValue: fieldValue, invalidValue: true }); } - } + }; render() { - let redirect = null; - if (this.needRedirect) { - let urlParams = new URLSearchParams(this.props.location.search); - if (this.state.filterValue !== null) { - urlParams.set(this.props.filterName, this.state.filterValue); - } else { - urlParams.delete(this.props.filterName); - } - redirect = ; - this.needRedirect = false; - } let active = this.state.filterValue !== null; return (
- {redirect} + value={this.state.fieldValue} inline={true} small={true}/>
); } diff --git a/frontend/src/components/objects/Connection.js b/frontend/src/components/objects/Connection.js index e0e942a..96f2235 100644 --- a/frontend/src/components/objects/Connection.js +++ b/frontend/src/components/objects/Connection.js @@ -23,6 +23,7 @@ import {dateTimeToTime, durationBetween, formatSize} from "../../utils"; import ButtonField from "../fields/ButtonField"; import LinkPopover from "./LinkPopover"; import TextField from "../fields/TextField"; +import dispatcher from "../../dispatcher"; const classNames = require('classnames'); @@ -99,7 +100,8 @@ class Connection extends Component { this.props.addServicePortFilter(conn["port_dst"])}/> + onClick={() => dispatcher.dispatch("connections_filters", + {"service_port": conn["port_dst"].toString()})}/> {conn["ip_src"]} diff --git a/frontend/src/components/objects/ConnectionMatchedRules.js b/frontend/src/components/objects/ConnectionMatchedRules.js index 73d5c5d..92bde49 100644 --- a/frontend/src/components/objects/ConnectionMatchedRules.js +++ b/frontend/src/components/objects/ConnectionMatchedRules.js @@ -18,20 +18,25 @@ import React, {Component} from 'react'; import './ConnectionMatchedRules.scss'; import ButtonField from "../fields/ButtonField"; +import dispatcher from "../../dispatcher"; +import {withRouter} from "react-router-dom"; class ConnectionMatchedRules extends Component { - constructor(props) { - super(props); - this.state = { - }; - } + onMatchedRulesSelected = (id) => { + const params = new URLSearchParams(this.props.location.search); + const rules = params.getAll("matched_rules"); + if (!rules.includes(id)) { + rules.push(id); + dispatcher.dispatch("connections_filters",{"matched_rules": rules}); + } + }; render() { const matchedRules = this.props.matchedRules.map(mr => { const rule = this.props.rules.find(r => r.id === mr); - return this.props.addMatchedRulesFilter(rule.id)} name={rule.name} - color={rule.color} small />; + return this.onMatchedRulesSelected(rule.id)} name={rule.name} + color={rule.color} small/>; }); return ( @@ -43,4 +48,4 @@ class ConnectionMatchedRules extends Component { } } -export default ConnectionMatchedRules; +export default withRouter(ConnectionMatchedRules); diff --git a/frontend/src/components/pages/MainPage.js b/frontend/src/components/pages/MainPage.js index da57e1a..0b06f55 100644 --- a/frontend/src/components/pages/MainPage.js +++ b/frontend/src/components/pages/MainPage.js @@ -29,7 +29,6 @@ import Header from "../Header"; import Filters from "../dialogs/Filters"; import MainPane from "../panels/MainPane"; import SearchPane from "../panels/SearchPane"; -import FiltersDispatcher from "../filters/FiltersDispatcher"; class MainPage extends Component { @@ -70,8 +69,6 @@ class MainPage extends Component {
- -
); diff --git a/frontend/src/components/panels/ConnectionsPane.js b/frontend/src/components/panels/ConnectionsPane.js index 1f79ab8..33dd7c1 100644 --- a/frontend/src/components/panels/ConnectionsPane.js +++ b/frontend/src/components/panels/ConnectionsPane.js @@ -19,13 +19,13 @@ import React, {Component} from 'react'; import './ConnectionsPane.scss'; import Connection from "../objects/Connection"; import Table from 'react-bootstrap/Table'; -import {Redirect} from 'react-router'; import {withRouter} from "react-router-dom"; import backend from "../../backend"; import ConnectionMatchedRules from "../objects/ConnectionMatchedRules"; import log from "../../log"; import ButtonField from "../fields/ButtonField"; import dispatcher from "../../dispatcher"; +import {Redirect} from "react-router"; const classNames = require('classnames'); @@ -50,60 +50,91 @@ class ConnectionsPane extends Component { } componentDidMount() { - const initialParams = {limit: this.queryLimit}; + let urlParams = new URLSearchParams(this.props.location.search); + this.setState({urlParams}); + + const additionalParams = {limit: this.queryLimit}; const match = this.props.location.pathname.match(/^\/connections\/([a-f0-9]{24})$/); if (match != null) { const id = match[1]; - initialParams.from = id; + additionalParams.from = id; backend.get(`/api/connections/${id}`) - .then(res => this.connectionSelected(res.json, false)) + .then(res => this.connectionSelected(res.json)) .catch(error => log.error("Error loading initial connection", error)); } - this.loadConnections(initialParams, true).then(() => log.debug("Connections loaded")); + this.loadConnections(additionalParams, urlParams, true).then(() => log.debug("Connections loaded")); + + this.connectionsFiltersCallback = payload => { + const params = this.state.urlParams; + const initialParams = params.toString(); + + Object.entries(payload).forEach(([key, value]) => { + if (value == null) { + params.delete(key); + } else if (Array.isArray(value)) { + params.delete(key); + value.forEach(v => params.append(key, v)); + } else { + params.set(key, value); + } + }); + + if (initialParams === params.toString()) { + return; + } - dispatcher.register("timeline_updates", payload => { + log.debug("Update following url params:", payload); + this.queryStringRedirect = true; + this.setState({urlParams}); + + this.loadConnections({limit: this.queryLimit}, urlParams) + .then(() => log.info("ConnectionsPane reloaded after query string update")); + }; + dispatcher.register("connections_filters", this.connectionsFiltersCallback); + + this.timelineUpdatesCallback = payload => { this.connectionsListRef.current.scrollTop = 0; this.loadConnections({ started_after: Math.round(payload.from.getTime() / 1000), started_before: Math.round(payload.to.getTime() / 1000), limit: this.maxConnections }).then(() => log.info(`Loading connections between ${payload.from} and ${payload.to}`)); - }); + }; + dispatcher.register("timeline_updates", this.timelineUpdatesCallback); - dispatcher.register("notifications", payload => { + this.notificationsCallback = payload => { if (payload.event === "rules.new" || payload.event === "rules.edit") { this.loadRules().then(() => log.debug("Loaded connection rules after notification update")); } - }); - - dispatcher.register("notifications", payload => { if (payload.event === "services.edit") { this.loadServices().then(() => log.debug("Services reloaded after notification update")); } - }); + }; + dispatcher.register("notifications", this.notificationsCallback); - dispatcher.register("pulse_connections_view", payload => { + this.pulseConnectionsViewCallback = payload => { this.setState({pulseConnectionsView: true}); setTimeout(() => this.setState({pulseConnectionsView: false}), payload.duration); - }); + }; + dispatcher.register("pulse_connections_view", this.pulseConnectionsViewCallback); + } + + componentWillUnmount() { + dispatcher.unregister(this.timelineUpdatesCallback); + dispatcher.unregister(this.notificationsCallback); + dispatcher.unregister(this.pulseConnectionsViewCallback); + dispatcher.unregister(this.connectionsFiltersCallback); } - connectionSelected = (c, doRedirect = true) => { - this.doSelectedConnectionRedirect = doRedirect; + connectionSelected = (c) => { + this.connectionSelectedRedirect = true; this.setState({selected: c.id}); this.props.onSelected(c); log.debug(`Connection ${c.id} selected`); }; - componentDidUpdate(prevProps, prevState, snapshot) { - if (prevProps.location.search !== this.props.location.search) { - this.loadConnections({limit: this.queryLimit}) - .then(() => log.info("ConnectionsPane reloaded after query string update")); - } - } - handleScroll = (e) => { if (this.disableScrollHandler) { this.lastScrollPosition = e.currentTarget.scrollTop; @@ -135,27 +166,12 @@ class ConnectionsPane extends Component { this.lastScrollPosition = e.currentTarget.scrollTop; }; - addServicePortFilter = (port) => { - const urlParams = new URLSearchParams(this.props.location.search); - urlParams.set("service_port", port); - this.doQueryStringRedirect = true; - this.setState({queryString: urlParams}); - }; - - addMatchedRulesFilter = (matchedRule) => { - const urlParams = new URLSearchParams(this.props.location.search); - const oldMatchedRules = urlParams.getAll("matched_rules") || []; - - if (!oldMatchedRules.includes(matchedRule)) { - urlParams.append("matched_rules", matchedRule); - this.doQueryStringRedirect = true; - this.setState({queryString: urlParams}); + async loadConnections(additionalParams, initialParams = null, isInitial = false) { + if (!initialParams) { + initialParams = this.state.urlParams; } - }; - - async loadConnections(params, isInitial = false) { - const urlParams = new URLSearchParams(this.props.location.search); - for (const [name, value] of Object.entries(params)) { + const urlParams = new URLSearchParams(initialParams.toString()); + for (const [name, value] of Object.entries(additionalParams)) { urlParams.set(name, value); } @@ -173,7 +189,7 @@ class ConnectionsPane extends Component { let firstConnection = this.state.firstConnection; let lastConnection = this.state.lastConnection; - if (params !== undefined && params.from !== undefined && params.to === undefined) { + if (additionalParams !== undefined && additionalParams.from !== undefined && additionalParams.to === undefined) { if (res.length > 0) { if (!isInitial) { res = res.slice(1); @@ -189,7 +205,7 @@ class ConnectionsPane extends Component { firstConnection = connections[0]; } } - } else if (params !== undefined && params.to !== undefined && params.from === undefined) { + } else if (additionalParams !== undefined && additionalParams.to !== undefined && additionalParams.from === undefined) { if (res.length > 0) { connections = res.slice(0, res.length - 1).concat(this.state.connections); firstConnection = connections[0]; @@ -235,12 +251,12 @@ class ConnectionsPane extends Component { render() { let redirect; - if (this.doSelectedConnectionRedirect) { - redirect = ; - this.doSelectedConnectionRedirect = false; - } else if (this.doQueryStringRedirect) { - redirect = ; - this.doQueryStringRedirect = false; + if (this.connectionSelectedRedirect) { + redirect = ; + this.connectionSelectedRedirect = false; + } else if (this.queryStringRedirect) { + redirect = ; + this.queryStringRedirect = false; } let loading = null; @@ -288,12 +304,10 @@ class ConnectionsPane extends Component { selected={this.state.selected === c.id} onMarked={marked => c.marked = marked} onEnabled={enabled => c.hidden = !enabled} - addServicePortFilter={this.addServicePortFilter} services={this.state.services}/>, c.matched_rules.length > 0 && + rules={this.state.rules}/> ]; }) } diff --git a/frontend/src/components/panels/SearchPane.scss b/frontend/src/components/panels/SearchPane.scss index 15fc7da..63e11fb 100644 --- a/frontend/src/components/panels/SearchPane.scss +++ b/frontend/src/components/panels/SearchPane.scss @@ -4,6 +4,7 @@ .searches-list { overflow: hidden; + flex: 2 1; .section-content { height: 100%; diff --git a/frontend/src/components/panels/StreamsPane.js b/frontend/src/components/panels/StreamsPane.js index bd1964e..1aa5c53 100644 --- a/frontend/src/components/panels/StreamsPane.js +++ b/frontend/src/components/panels/StreamsPane.js @@ -107,7 +107,7 @@ class StreamsPane extends Component { const json = JSON.parse(m.body); body = ; } catch (e) { - console.log(e); + log.error(e); } } diff --git a/frontend/src/dispatcher.js b/frontend/src/dispatcher.js index 943f7ec..fa08d48 100644 --- a/frontend/src/dispatcher.js +++ b/frontend/src/dispatcher.js @@ -15,6 +15,8 @@ * along with this program. If not, see . */ +const _ = require('lodash'); + class Dispatcher { constructor() { @@ -44,6 +46,10 @@ class Dispatcher { } }; + unregister = (callback) => { + this.listeners = _.without(callback); + }; + } const dispatcher = new Dispatcher(); diff --git a/search_controller.go b/search_controller.go index 723cd93..5ed762a 100644 --- a/search_controller.go +++ b/search_controller.go @@ -76,6 +76,10 @@ func NewSearchController(storage Storage) *SearchController { log.WithError(err).Panic("failed to retrieve performed searches") } + if searches == nil { + searches = []PerformedSearch{} + } + return &SearchController{ storage: storage, performedSearches: searches, diff --git a/statistics_controller.go b/statistics_controller.go index 57c7d95..fda7494 100644 --- a/statistics_controller.go +++ b/statistics_controller.go @@ -26,19 +26,19 @@ import ( type StatisticRecord struct { RangeStart time.Time `json:"range_start" bson:"_id"` - ConnectionsPerService map[uint16]int `json:"connections_per_service,omitempty" bson:"connections_per_service"` - ClientBytesPerService map[uint16]int `json:"client_bytes_per_service,omitempty" bson:"client_bytes_per_service"` - ServerBytesPerService map[uint16]int `json:"server_bytes_per_service,omitempty" bson:"server_bytes_per_service"` - TotalBytesPerService map[uint16]int `json:"total_bytes_per_service,omitempty" bson:"total_bytes_per_service"` - DurationPerService map[uint16]int64 `json:"duration_per_service,omitempty" bson:"duration_per_service"` - MatchedRules map[RowID]int64 `json:"matched_rules,omitempty" bson:"matched_rules"` + ConnectionsPerService map[uint16]int `json:"connections_per_service" bson:"connections_per_service"` + ClientBytesPerService map[uint16]int `json:"client_bytes_per_service" bson:"client_bytes_per_service"` + ServerBytesPerService map[uint16]int `json:"server_bytes_per_service" bson:"server_bytes_per_service"` + TotalBytesPerService map[uint16]int `json:"total_bytes_per_service" bson:"total_bytes_per_service"` + DurationPerService map[uint16]int64 `json:"duration_per_service" bson:"duration_per_service"` + MatchedRules map[string]int64 `json:"matched_rules" bson:"matched_rules"` } type StatisticsFilter struct { RangeFrom time.Time `form:"range_from"` RangeTo time.Time `form:"range_to"` Ports []uint16 `form:"ports"` - RulesIDs []RowID `form:"rules_ids"` + RulesIDs []string `form:"rules_ids"` Metric string `form:"metric"` } @@ -57,7 +57,7 @@ func NewStatisticsController(storage Storage) StatisticsController { func (sc *StatisticsController) GetStatistics(context context.Context, filter StatisticsFilter) []StatisticRecord { var statisticRecords []StatisticRecord - query := sc.storage.Find(Statistics).Context(context) + query := sc.storage.Find(Statistics).Context(context).Sort("_id", true) if !filter.RangeFrom.IsZero() { query = query.Filter(OrderedDocument{{"_id", UnorderedDocument{"$lt": filter.RangeFrom}}}) } @@ -81,7 +81,7 @@ func (sc *StatisticsController) GetStatistics(context context.Context, filter St } for _, ruleID := range filter.RulesIDs { if filter.Metric == "" || filter.Metric == "matched_rules" { - query = query.Projection(OrderedDocument{{fmt.Sprintf("matched_rules.%s", ruleID.Hex()), 1}}) + query = query.Projection(OrderedDocument{{fmt.Sprintf("matched_rules.%s", ruleID), 1}}) } } @@ -91,6 +91,7 @@ func (sc *StatisticsController) GetStatistics(context context.Context, filter St } } + log.Println(query) if err := query.All(&statisticRecords); err != nil { log.WithError(err).WithField("filter", filter).Error("failed to retrieve statistics") return []StatisticRecord{} -- cgit v1.2.3-70-g09d2 From d4bac2d6741f7a291522c29c9ecc87c3e32e21d4 Mon Sep 17 00:00:00 2001 From: Emiliano Ciavatta Date: Fri, 16 Oct 2020 14:16:44 +0200 Subject: Add notification when pcap have been processed --- application_context.go | 12 +- caronte.go | 2 + frontend/src/components/App.js | 6 +- frontend/src/components/Notifications.js | 17 +- frontend/src/components/Timeline.js | 88 +++++---- frontend/src/components/fields/ButtonField.js | 2 +- frontend/src/components/fields/ChoiceField.js | 10 +- frontend/src/components/objects/Connection.js | 36 +--- .../components/objects/ConnectionMatchedRules.js | 10 +- frontend/src/components/objects/CopyLinkPopover.js | 54 ++++++ frontend/src/components/objects/MessageAction.js | 15 +- frontend/src/components/panels/ConnectionsPane.js | 107 +++++------ frontend/src/components/panels/PcapsPane.js | 40 ++-- frontend/src/components/panels/RulesPane.js | 51 ++--- frontend/src/components/panels/SearchPane.js | 34 ++-- frontend/src/components/panels/ServicesPane.js | 68 ++++--- frontend/src/serviceWorker.js | 208 ++++++++++----------- parsers/http_request_parser.go | 12 +- pcap_importer.go | 39 ++-- 19 files changed, 458 insertions(+), 353 deletions(-) create mode 100644 frontend/src/components/objects/CopyLinkPopover.js (limited to 'frontend/src/components/objects/ConnectionMatchedRules.js') diff --git a/application_context.go b/application_context.go index 8abb6f4..0410b88 100644 --- a/application_context.go +++ b/application_context.go @@ -39,6 +39,7 @@ type ApplicationContext struct { ConnectionStreamsController ConnectionStreamsController SearchController *SearchController StatisticsController StatisticsController + NotificationController *NotificationController IsConfigured bool Version string } @@ -70,13 +71,12 @@ func CreateApplicationContext(storage Storage, version string) (*ApplicationCont Version: version, } - applicationContext.configure() return applicationContext, nil } func (sm *ApplicationContext) SetConfig(config Config) { sm.Config = config - sm.configure() + sm.Configure() var upsertResults interface{} if _, err := sm.Storage.Update(Settings).Upsert(&upsertResults). Filter(OrderedDocument{{"_id", "config"}}).One(UnorderedDocument{"config": config}); err != nil { @@ -93,7 +93,11 @@ func (sm *ApplicationContext) SetAccounts(accounts gin.Accounts) { } } -func (sm *ApplicationContext) configure() { +func (sm *ApplicationContext) SetNotificationController(notificationController *NotificationController) { + sm.NotificationController = notificationController +} + +func (sm *ApplicationContext) Configure() { if sm.IsConfigured { return } @@ -110,7 +114,7 @@ func (sm *ApplicationContext) configure() { log.WithError(err).Panic("failed to create a RulesManager") } sm.RulesManager = rulesManager - sm.PcapImporter = NewPcapImporter(sm.Storage, *serverNet, sm.RulesManager) + sm.PcapImporter = NewPcapImporter(sm.Storage, *serverNet, sm.RulesManager, sm.NotificationController) sm.ServicesController = NewServicesController(sm.Storage) sm.SearchController = NewSearchController(sm.Storage) sm.ConnectionsController = NewConnectionsController(sm.Storage, sm.SearchController, sm.ServicesController) diff --git a/caronte.go b/caronte.go index 2d24af6..95f00ef 100644 --- a/caronte.go +++ b/caronte.go @@ -51,10 +51,12 @@ func main() { notificationController := NewNotificationController(applicationContext) go notificationController.Run() + applicationContext.SetNotificationController(notificationController) resourcesController := NewResourcesController(notificationController) go resourcesController.Run() + applicationContext.Configure() applicationRouter := CreateApplicationRouter(applicationContext, notificationController, resourcesController) if applicationRouter.Run(fmt.Sprintf("%s:%v", *bindAddress, *bindPort)) != nil { log.WithError(err).WithFields(logFields).Fatal("failed to create the server") diff --git a/frontend/src/components/App.js b/frontend/src/components/App.js index 0f700db..888ff86 100644 --- a/frontend/src/components/App.js +++ b/frontend/src/components/App.js @@ -15,10 +15,10 @@ * along with this program. If not, see . */ -import React, {Component} from 'react'; -import ConfigurationPage from "./pages/ConfigurationPage"; -import Notifications from "./Notifications"; +import React, {Component} from "react"; import dispatcher from "../dispatcher"; +import Notifications from "./Notifications"; +import ConfigurationPage from "./pages/ConfigurationPage"; import MainPage from "./pages/MainPage"; import ServiceUnavailablePage from "./pages/ServiceUnavailablePage"; diff --git a/frontend/src/components/Notifications.js b/frontend/src/components/Notifications.js index 56a4508..92731d9 100644 --- a/frontend/src/components/Notifications.js +++ b/frontend/src/components/Notifications.js @@ -17,6 +17,7 @@ import React, {Component} from "react"; import dispatcher from "../dispatcher"; +import {randomClassName} from "../utils"; import "./Notifications.scss"; const _ = require("lodash"); @@ -30,9 +31,15 @@ class Notifications extends Component { }; componentDidMount() { - dispatcher.register("notifications", n => this.notificationHandler(n)); + dispatcher.register("notifications", this.handleNotifications); } + componentWillUnmount() { + dispatcher.unregister(this.handleNotifications); + } + + handleNotifications = (n) => this.notificationHandler(n); + notificationHandler = (n) => { switch (n.event) { case "connected": @@ -54,6 +61,11 @@ class Notifications extends Component { n.description = `existing rule updated: ${n.message["name"]}`; n.variant = "blue"; return this.pushNotification(n); + case "pcap.completed": + n.title = "new pcap analyzed"; + n.description = `${n.message["processed_packets"]} packets processed`; + n.variant = "blue"; + return this.pushNotification(n); default: return; } @@ -61,6 +73,7 @@ class Notifications extends Component { pushNotification = (notification) => { const notifications = this.state.notifications; + notification.id = randomClassName(); notifications.push(notification); this.setState({notifications}); setTimeout(() => { @@ -103,7 +116,7 @@ class Notifications extends Component { if (n.variant) { notificationClassnames[`notification-${n.variant}`] = true; } - return
+ return

{n.title}

{n.description}
; diff --git a/frontend/src/components/Timeline.js b/frontend/src/components/Timeline.js index 8d1fd40..94fa4d0 100644 --- a/frontend/src/components/Timeline.js +++ b/frontend/src/components/Timeline.js @@ -15,8 +15,9 @@ * along with this program. If not, see . */ -import React, {Component} from 'react'; -import './Timeline.scss'; +import {TimeRange, TimeSeries} from "pondjs"; +import React, {Component} from "react"; +import {withRouter} from "react-router-dom"; import { ChartContainer, ChartRow, @@ -27,15 +28,14 @@ import { styler, YAxis } from "react-timeseries-charts"; -import {TimeRange, TimeSeries} from "pondjs"; import backend from "../backend"; -import ChoiceField from "./fields/ChoiceField"; -import {withRouter} from "react-router-dom"; -import log from "../log"; import dispatcher from "../dispatcher"; +import log from "../log"; +import ChoiceField from "./fields/ChoiceField"; +import "./Timeline.scss"; const minutes = 60 * 1000; -const classNames = require('classnames'); +const classNames = require("classnames"); const leftSelectionPaddingMultiplier = 24; const rightSelectionPaddingMultiplier = 8; @@ -61,42 +61,17 @@ class Timeline extends Component { }); this.loadStatistics(this.state.metric).then(() => log.debug("Statistics loaded after mount")); - - this.connectionsFiltersCallback = (payload) => { - if ("service_port" in payload && this.state.servicePortFilter !== payload["service_port"]) { - this.setState({servicePortFilter: payload["service_port"]}); - this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after service port changed")); - } - if ("matched_rules" in payload && this.state.matchedRulesFilter !== payload["matched_rules"]) { - this.setState({matchedRulesFilter: payload["matched_rules"]}); - this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after matched rules changed")); - } - }; - dispatcher.register("connections_filters", this.connectionsFiltersCallback); - - 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.state.metric !== "matched_rules") { - this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after services updates")); - } else if (payload.event.startsWith("rules") && this.state.metric === "matched_rules") { - this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after rules updates")); - } - }); - - dispatcher.register("pulse_timeline", (payload) => { - this.setState({pulseTimeline: true}); - setTimeout(() => this.setState({pulseTimeline: false}), payload.duration); - }); + dispatcher.register("connections_filters", this.handleConnectionsFiltersCallback); + dispatcher.register("connection_updates", this.handleConnectionUpdates); + dispatcher.register("notifications", this.handleNotifications); + dispatcher.register("pulse_timeline", this.handlePulseTimeline); } componentWillUnmount() { - dispatcher.unregister(this.connectionsFiltersCallback); + dispatcher.unregister(this.handleConnectionsFiltersCallback); + dispatcher.unregister(this.handleConnectionUpdates); + dispatcher.unregister(this.handleNotifications); + dispatcher.unregister(this.handlePulseTimeline); } loadStatistics = async (metric) => { @@ -217,6 +192,39 @@ class Timeline extends Component { }, 1000); }; + handleConnectionsFiltersCallback = (payload) => { + if ("service_port" in payload && this.state.servicePortFilter !== payload["service_port"]) { + this.setState({servicePortFilter: payload["service_port"]}); + this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after service port changed")); + } + if ("matched_rules" in payload && this.state.matchedRulesFilter !== payload["matched_rules"]) { + this.setState({matchedRulesFilter: payload["matched_rules"]}); + this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after matched rules changed")); + } + }; + + handleConnectionUpdates = (payload) => { + this.setState({ + selection: new TimeRange(payload.from, payload.to), + }); + this.adjustSelection(); + }; + + handleNotifications = (payload) => { + if (payload.event === "services.edit" && this.state.metric !== "matched_rules") { + this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after services updates")); + } else if (payload.event.startsWith("rules") && this.state.metric === "matched_rules") { + this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after rules updates")); + } else if (payload.event === "pcap.completed") { + this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after pcap processed")); + } + }; + + handlePulseTimeline = (payload) => { + this.setState({pulseTimeline: true}); + setTimeout(() => this.setState({pulseTimeline: false}), payload.duration); + }; + adjustSelection = () => { const seriesRange = this.state.series.range(); const selection = this.state.selection; diff --git a/frontend/src/components/fields/ButtonField.js b/frontend/src/components/fields/ButtonField.js index de747a5..15ef179 100644 --- a/frontend/src/components/fields/ButtonField.js +++ b/frontend/src/components/fields/ButtonField.js @@ -58,7 +58,7 @@ class ButtonField extends Component {
+ onClick={handler} style={buttonStyle} disabled={this.props.disabled}>{this.props.name}
); } diff --git a/frontend/src/components/fields/ChoiceField.js b/frontend/src/components/fields/ChoiceField.js index 14071c3..7e97d89 100644 --- a/frontend/src/components/fields/ChoiceField.js +++ b/frontend/src/components/fields/ChoiceField.js @@ -15,12 +15,12 @@ * along with this program. If not, see . */ -import React, {Component} from 'react'; -import './ChoiceField.scss'; -import './common.scss'; +import React, {Component} from "react"; import {randomClassName} from "../../utils"; +import "./ChoiceField.scss"; +import "./common.scss"; -const classNames = require('classnames'); +const classNames = require("classnames"); class ChoiceField extends Component { @@ -67,7 +67,7 @@ class ChoiceField extends Component { } return ( -
{!inline && name && }
{ if (name === "hide") { const enabled = !this.props.data.hidden; backend.post(`/api/connections/${this.props.data.id}/${enabled ? "hide" : "show"}`) @@ -57,13 +50,7 @@ class Connection extends Component { this.setState({update: true}); }); } - if (name === "copy") { - this.copyTextarea.current.select(); - document.execCommand("copy"); - this.setState({copiedMessage: true}); - setTimeout(() => this.setState({copiedMessage: false}), 3000); - } - } + }; render() { let conn = this.props.data; @@ -88,12 +75,6 @@ class Connection extends Component { {conn.comment && }
; - const copyPopoverContent =
- {this.state.copiedMessage ? Copied! : - Click to copy the connection id} - -
; - return ( 0})}> @@ -121,9 +102,8 @@ class Connection extends Component { this.handleAction("comment")}>@} content={commentPopoverContent} placement="right"/> - this.handleAction("copy")}>#} - content={copyPopoverContent} placement="right"/> + ); diff --git a/frontend/src/components/objects/ConnectionMatchedRules.js b/frontend/src/components/objects/ConnectionMatchedRules.js index 92bde49..cfd1254 100644 --- a/frontend/src/components/objects/ConnectionMatchedRules.js +++ b/frontend/src/components/objects/ConnectionMatchedRules.js @@ -15,11 +15,11 @@ * along with this program. If not, see . */ -import React, {Component} from 'react'; -import './ConnectionMatchedRules.scss'; -import ButtonField from "../fields/ButtonField"; -import dispatcher from "../../dispatcher"; +import React, {Component} from "react"; import {withRouter} from "react-router-dom"; +import dispatcher from "../../dispatcher"; +import ButtonField from "../fields/ButtonField"; +import "./ConnectionMatchedRules.scss"; class ConnectionMatchedRules extends Component { @@ -28,7 +28,7 @@ class ConnectionMatchedRules extends Component { const rules = params.getAll("matched_rules"); if (!rules.includes(id)) { rules.push(id); - dispatcher.dispatch("connections_filters",{"matched_rules": rules}); + dispatcher.dispatch("connections_filters", {"matched_rules": rules}); } }; diff --git a/frontend/src/components/objects/CopyLinkPopover.js b/frontend/src/components/objects/CopyLinkPopover.js new file mode 100644 index 0000000..fa9266f --- /dev/null +++ b/frontend/src/components/objects/CopyLinkPopover.js @@ -0,0 +1,54 @@ +/* + * This file is part of caronte (https://github.com/eciavatta/caronte). + * Copyright (c) 2020 Emiliano Ciavatta. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import React, {Component} from "react"; +import TextField from "../fields/TextField"; +import LinkPopover from "./LinkPopover"; + +class CopyLinkPopover extends Component { + + state = {}; + + constructor(props) { + super(props); + + this.copyTextarea = React.createRef(); + } + + handleClick = () => { + this.copyTextarea.current.select(); + document.execCommand("copy"); + this.setState({copiedMessage: true}); + setTimeout(() => this.setState({copiedMessage: false}), 3000); + }; + + render() { + const copyPopoverContent =
+ {this.state.copiedMessage ? Copied! : + Click to copy} + +
; + + return ( + {this.props.text}} + content={copyPopoverContent} placement="right"/> + ); + } +} + +export default CopyLinkPopover; diff --git a/frontend/src/components/objects/MessageAction.js b/frontend/src/components/objects/MessageAction.js index 2b46320..e0c96e8 100644 --- a/frontend/src/components/objects/MessageAction.js +++ b/frontend/src/components/objects/MessageAction.js @@ -15,11 +15,11 @@ * along with this program. If not, see . */ -import React, {Component} from 'react'; -import './MessageAction.scss'; +import React, {Component} from "react"; import {Modal} from "react-bootstrap"; -import TextField from "../fields/TextField"; import ButtonField from "../fields/ButtonField"; +import TextField from "../fields/TextField"; +import "./MessageAction.scss"; class MessageAction extends Component { @@ -34,7 +34,7 @@ class MessageAction extends Component { copyActionValue() { this.actionValue.current.select(); - document.execCommand('copy'); + document.execCommand("copy"); this.setState({copyButtonText: "copied!"}); setTimeout(() => this.setState({copyButtonText: "copy"}), 3000); } @@ -54,11 +54,12 @@ class MessageAction extends Component { - + - - + + ); diff --git a/frontend/src/components/panels/ConnectionsPane.js b/frontend/src/components/panels/ConnectionsPane.js index ea47059..23c6114 100644 --- a/frontend/src/components/panels/ConnectionsPane.js +++ b/frontend/src/components/panels/ConnectionsPane.js @@ -15,20 +15,20 @@ * along with this program. If not, see . */ -import React, {Component} from 'react'; -import './ConnectionsPane.scss'; -import Connection from "../objects/Connection"; -import Table from 'react-bootstrap/Table'; +import React, {Component} from "react"; +import Table from "react-bootstrap/Table"; +import {Redirect} from "react-router"; import {withRouter} from "react-router-dom"; import backend from "../../backend"; -import ConnectionMatchedRules from "../objects/ConnectionMatchedRules"; -import log from "../../log"; -import ButtonField from "../fields/ButtonField"; import dispatcher from "../../dispatcher"; -import {Redirect} from "react-router"; +import log from "../../log"; import {updateParams} from "../../utils"; +import ButtonField from "../fields/ButtonField"; +import Connection from "../objects/Connection"; +import ConnectionMatchedRules from "../objects/ConnectionMatchedRules"; +import "./ConnectionsPane.scss"; -const classNames = require('classnames'); +const classNames = require("classnames"); class ConnectionsPane extends Component { @@ -67,55 +67,56 @@ class ConnectionsPane extends Component { this.loadConnections(additionalParams, urlParams, true).then(() => log.debug("Connections loaded")); - this.connectionsFiltersCallback = payload => { - const newParams = updateParams(this.state.urlParams, payload); - if (this.state.urlParams.toString() === newParams.toString()) { - return; - } - - log.debug("Update following url params:", payload); - this.queryStringRedirect = true; - this.setState({urlParams: newParams}); - - this.loadConnections({limit: this.queryLimit}, newParams) - .then(() => log.info("ConnectionsPane reloaded after query string update")); - }; - dispatcher.register("connections_filters", this.connectionsFiltersCallback); - - this.timelineUpdatesCallback = payload => { - this.connectionsListRef.current.scrollTop = 0; - this.loadConnections({ - "started_after": Math.round(payload.from.getTime() / 1000), - "started_before": Math.round(payload.to.getTime() / 1000), - limit: this.maxConnections - }).then(() => log.info(`Loading connections between ${payload.from} and ${payload.to}`)); - }; - dispatcher.register("timeline_updates", this.timelineUpdatesCallback); - - this.notificationsCallback = payload => { - if (payload.event === "rules.new" || payload.event === "rules.edit") { - this.loadRules().then(() => log.debug("Loaded connection rules after notification update")); - } - if (payload.event === "services.edit") { - this.loadServices().then(() => log.debug("Services reloaded after notification update")); - } - }; - dispatcher.register("notifications", this.notificationsCallback); - - this.pulseConnectionsViewCallback = payload => { - this.setState({pulseConnectionsView: true}); - setTimeout(() => this.setState({pulseConnectionsView: false}), payload.duration); - }; - dispatcher.register("pulse_connections_view", this.pulseConnectionsViewCallback); + dispatcher.register("connections_filters", this.handleConnectionsFilters); + dispatcher.register("timeline_updates", this.handleTimelineUpdates); + dispatcher.register("notifications", this.handleNotifications); + dispatcher.register("pulse_connections_view", this.handlePulseConnectionsView); } componentWillUnmount() { - dispatcher.unregister(this.timelineUpdatesCallback); - dispatcher.unregister(this.notificationsCallback); - dispatcher.unregister(this.pulseConnectionsViewCallback); - dispatcher.unregister(this.connectionsFiltersCallback); + dispatcher.unregister(this.handleConnectionsFilters); + dispatcher.unregister(this.handleTimelineUpdates); + dispatcher.unregister(this.handleNotifications); + dispatcher.unregister(this.handlePulseConnectionsView); } + handleConnectionsFilters = (payload) => { + const newParams = updateParams(this.state.urlParams, payload); + if (this.state.urlParams.toString() === newParams.toString()) { + return; + } + + log.debug("Update following url params:", payload); + this.queryStringRedirect = true; + this.setState({urlParams: newParams}); + + this.loadConnections({limit: this.queryLimit}, newParams) + .then(() => log.info("ConnectionsPane reloaded after query string update")); + }; + + handleTimelineUpdates = (payload) => { + this.connectionsListRef.current.scrollTop = 0; + this.loadConnections({ + "started_after": Math.round(payload.from.getTime() / 1000), + "started_before": Math.round(payload.to.getTime() / 1000), + limit: this.maxConnections + }).then(() => log.info(`Loading connections between ${payload.from} and ${payload.to}`)); + }; + + handleNotifications = (payload) => { + if (payload.event === "rules.new" || payload.event === "rules.edit") { + this.loadRules().then(() => log.debug("Loaded connection rules after notification update")); + } + if (payload.event === "services.edit") { + this.loadServices().then(() => log.debug("Services reloaded after notification update")); + } + }; + + handlePulseConnectionsView = (payload) => { + this.setState({pulseConnectionsView: true}); + setTimeout(() => this.setState({pulseConnectionsView: false}), payload.duration); + }; + connectionSelected = (c) => { this.connectionSelectedRedirect = true; this.setState({selected: c.id}); diff --git a/frontend/src/components/panels/PcapsPane.js b/frontend/src/components/panels/PcapsPane.js index fd3db75..64e7804 100644 --- a/frontend/src/components/panels/PcapsPane.js +++ b/frontend/src/components/panels/PcapsPane.js @@ -24,6 +24,7 @@ import ButtonField from "../fields/ButtonField"; import CheckField from "../fields/CheckField"; import InputField from "../fields/InputField"; import TextField from "../fields/TextField"; +import CopyLinkPopover from "../objects/CopyLinkPopover"; import LinkPopover from "../objects/LinkPopover"; import "./common.scss"; import "./PcapsPane.scss"; @@ -44,16 +45,20 @@ class PcapsPane extends Component { componentDidMount() { this.loadSessions(); - - dispatcher.register("notifications", (payload) => { - if (payload.event === "pcap.upload" || payload.event === "pcap.file") { - this.loadSessions(); - } - }); - + dispatcher.register("notifications", this.handleNotifications); document.title = "caronte:~/pcaps$"; } + componentWillUnmount() { + dispatcher.unregister(this.handleNotifications); + } + + handleNotifications = (payload) => { + if (payload.event.startsWith("pcap")) { + this.loadSessions(); + } + }; + loadSessions = () => { backend.get("/api/pcap/sessions") .then((res) => this.setState({sessions: res.json, sessionsStatusCode: res.status})) @@ -130,10 +135,19 @@ class PcapsPane extends Component { }; render() { - let sessions = this.state.sessions.map((s) => - - {s["id"].substring(0, 8)} - {dateTimeToTime(s["started_at"])} + let sessions = this.state.sessions.map((s) => { + const startedAt = new Date(s["started_at"]); + const completedAt = new Date(s["completed_at"]); + let timeInfo =
+ Started at {startedAt.toLocaleDateString() + " " + startedAt.toLocaleTimeString()}
+ Completed at {completedAt.toLocaleDateString() + " " + completedAt.toLocaleTimeString()} +
; + + return + + + + {durationBetween(s["started_at"], s["completed_at"])} {formatSize(s["size"])} {s["processed_packets"]} @@ -143,8 +157,8 @@ class PcapsPane extends Component { placement="left"/> download - - ); + ; + }); const handleUploadFileChange = (file) => { this.setState({ diff --git a/frontend/src/components/panels/RulesPane.js b/frontend/src/components/panels/RulesPane.js index a66cde7..d872b47 100644 --- a/frontend/src/components/panels/RulesPane.js +++ b/frontend/src/components/panels/RulesPane.js @@ -15,26 +15,26 @@ * along with this program. If not, see . */ -import React, {Component} from 'react'; -import './common.scss'; -import './RulesPane.scss'; -import Table from "react-bootstrap/Table"; +import React, {Component} from "react"; import {Col, Container, Row} from "react-bootstrap"; -import InputField from "../fields/InputField"; -import CheckField from "../fields/CheckField"; -import TextField from "../fields/TextField"; +import Table from "react-bootstrap/Table"; import backend from "../../backend"; -import NumericField from "../fields/extensions/NumericField"; -import ColorField from "../fields/extensions/ColorField"; -import ChoiceField from "../fields/ChoiceField"; -import ButtonField from "../fields/ButtonField"; +import dispatcher from "../../dispatcher"; import validation from "../../validation"; +import ButtonField from "../fields/ButtonField"; +import CheckField from "../fields/CheckField"; +import ChoiceField from "../fields/ChoiceField"; +import ColorField from "../fields/extensions/ColorField"; +import NumericField from "../fields/extensions/NumericField"; +import InputField from "../fields/InputField"; +import TextField from "../fields/TextField"; +import CopyLinkPopover from "../objects/CopyLinkPopover"; import LinkPopover from "../objects/LinkPopover"; -import {randomClassName} from "../../utils"; -import dispatcher from "../../dispatcher"; +import "./common.scss"; +import "./RulesPane.scss"; -const classNames = require('classnames'); -const _ = require('lodash'); +const classNames = require("classnames"); +const _ = require("lodash"); class RulesPane extends Component { @@ -88,15 +88,20 @@ class RulesPane extends Component { this.reset(); this.loadRules(); - dispatcher.register("notifications", payload => { - if (payload.event === "rules.new" || payload.event === "rules.edit") { - this.loadRules(); - } - }); - + dispatcher.register("notifications", this.handleNotifications); document.title = "caronte:~/rules$"; } + componentWillUnmount() { + dispatcher.unregister(this.handleNotifications); + } + + handleNotifications = (payload) => { + if (payload.event === "rules.new" || payload.event === "rules.edit") { + this.loadRules(); + } + }; + loadRules = () => { backend.get("/api/rules").then(res => this.setState({rules: res.json, rulesStatusCode: res.status})) .catch(res => this.setState({rulesStatusCode: res.status, rulesResponse: JSON.stringify(res.json)})); @@ -249,7 +254,7 @@ class RulesPane extends Component { this.reset(); this.setState({selectedRule: _.cloneDeep(r)}); }} className={classNames("row-small", "row-clickable", {"row-selected": rule.id === r.id})}> - {r["id"].substring(0, 8)} + {r["name"]} {r["notes"]} @@ -260,7 +265,7 @@ class RulesPane extends Component { rule.patterns.concat(this.state.newPattern) : rule.patterns ).map(p => p === pattern ? - + { diff --git a/frontend/src/components/panels/SearchPane.js b/frontend/src/components/panels/SearchPane.js index d3c0c8b..d36e85e 100644 --- a/frontend/src/components/panels/SearchPane.js +++ b/frontend/src/components/panels/SearchPane.js @@ -60,15 +60,14 @@ class SearchPane extends Component { this.reset(); this.loadSearches(); - dispatcher.register("notifications", payload => { - if (payload.event === "searches.new") { - this.loadSearches(); - } - }); - + dispatcher.register("notifications", this.handleNotification); document.title = "caronte:~/searches$"; } + componentWillUnmount() { + dispatcher.unregister(this.handleNotification); + } + loadSearches = () => { backend.get("/api/searches") .then(res => this.setState({searches: res.json, searchesStatusCode: res.status})) @@ -77,14 +76,18 @@ class SearchPane extends Component { performSearch = () => { const options = this.state.currentSearchOptions; + this.setState({loading: true}); if (this.validateSearch(options)) { backend.post("/api/searches/perform", options).then(res => { this.reset(); - this.setState({searchStatusCode: res.status}); + this.setState({searchStatusCode: res.status, loading: false}); this.loadSearches(); this.viewSearch(res.json.id); }).catch(res => { - this.setState({searchStatusCode: res.status, searchResponse: JSON.stringify(res.json)}); + this.setState({ + searchStatusCode: res.status, searchResponse: JSON.stringify(res.json), + loading: false + }); }); } }; @@ -156,6 +159,12 @@ class SearchPane extends Component { dispatcher.dispatch("connections_filters", {"performed_search": searchId}); }; + handleNotification = (payload) => { + if (payload.event === "searches.new") { + this.loadSearches(); + } + }; + render() { const options = this.state.currentSearchOptions; @@ -263,7 +272,8 @@ class SearchPane extends Component { onChange={v => this.updateParam(s => s["regex_search"]["not_pattern"] = v)}/>
- this.updateParam(s => s["regex_search"]["case_insensitive"] = v)}/>
- - + +
diff --git a/frontend/src/components/panels/ServicesPane.js b/frontend/src/components/panels/ServicesPane.js index bc82356..48d9e29 100644 --- a/frontend/src/components/panels/ServicesPane.js +++ b/frontend/src/components/panels/ServicesPane.js @@ -15,24 +15,24 @@ * along with this program. If not, see . */ -import React, {Component} from 'react'; -import './common.scss'; -import './ServicesPane.scss'; -import Table from "react-bootstrap/Table"; +import React, {Component} from "react"; import {Col, Container, Row} from "react-bootstrap"; -import InputField from "../fields/InputField"; -import TextField from "../fields/TextField"; +import Table from "react-bootstrap/Table"; import backend from "../../backend"; -import NumericField from "../fields/extensions/NumericField"; -import ColorField from "../fields/extensions/ColorField"; -import ButtonField from "../fields/ButtonField"; +import dispatcher from "../../dispatcher"; +import {createCurlCommand} from "../../utils"; import validation from "../../validation"; +import ButtonField from "../fields/ButtonField"; +import ColorField from "../fields/extensions/ColorField"; +import NumericField from "../fields/extensions/NumericField"; +import InputField from "../fields/InputField"; +import TextField from "../fields/TextField"; import LinkPopover from "../objects/LinkPopover"; -import {createCurlCommand} from "../../utils"; -import dispatcher from "../../dispatcher"; +import "./common.scss"; +import "./ServicesPane.scss"; -const classNames = require('classnames'); -const _ = require('lodash'); +const classNames = require("classnames"); +const _ = require("lodash"); class ServicesPane extends Component { @@ -52,15 +52,20 @@ class ServicesPane extends Component { this.reset(); this.loadServices(); - dispatcher.register("notifications", payload => { - if (payload.event === "services.edit") { - this.loadServices(); - } - }); - + dispatcher.register("notifications", this.handleNotifications); document.title = "caronte:~/services$"; } + componentWillUnmount() { + dispatcher.unregister(this.handleNotifications); + } + + handleNotifications = (payload) => { + if (payload.event === "services.edit") { + this.loadServices(); + } + }; + loadServices = () => { backend.get("/api/services") .then(res => this.setState({services: Object.values(res.json), servicesStatusCode: res.status})) @@ -125,10 +130,10 @@ class ServicesPane extends Component { { this.reset(); this.setState({isUpdate: true, currentService: _.cloneDeep(s)}); - }} className={classNames("row-small", "row-clickable", {"row-selected": service.port === s.port })}> + }} className={classNames("row-small", "row-clickable", {"row-selected": service.port === s.port})}> {s["port"]} {s["name"]} - + {s["notes"]} ); @@ -141,9 +146,9 @@ class ServicesPane extends Component {
GET /api/services {this.state.servicesStatusCode && - } + }
@@ -170,7 +175,7 @@ class ServicesPane extends Component { PUT /api/services + placement="left"/>
@@ -179,17 +184,17 @@ class ServicesPane extends Component { this.updateParam((s) => s.port = v)} - min={0} max={65565} error={this.state.servicePortError} /> + min={0} max={65565} error={this.state.servicePortError}/> this.updateParam((s) => s.name = v)} - error={this.state.serviceNameError} /> + error={this.state.serviceNameError}/> this.updateParam((s) => s.color = v)} /> + onChange={(v) => this.updateParam((s) => s.color = v)}/> this.updateParam((s) => s.notes = v)} /> + onChange={(v) => this.updateParam((s) => s.notes = v)}/> @@ -199,8 +204,9 @@ class ServicesPane extends Component {
{} - +
diff --git a/frontend/src/serviceWorker.js b/frontend/src/serviceWorker.js index c633a91..a1f0ba8 100644 --- a/frontend/src/serviceWorker.js +++ b/frontend/src/serviceWorker.js @@ -11,131 +11,131 @@ // opt-in, read https://bit.ly/CRA-PWA const isLocalhost = Boolean( - window.location.hostname === 'localhost' || + window.location.hostname === "localhost" || // [::1] is the IPv6 localhost address. - window.location.hostname === '[::1]' || + window.location.hostname === "[::1]" || // 127.0.0.0/8 are considered localhost for IPv4. window.location.hostname.match( - /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ ) ); export function register(config) { - if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { - // The URL constructor is available in all browsers that support SW. - const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); - if (publicUrl.origin !== window.location.origin) { - // Our service worker won't work if PUBLIC_URL is on a different origin - // from what our page is served on. This might happen if a CDN is used to - // serve assets; see https://github.com/facebook/create-react-app/issues/2374 - return; - } + if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebook/create-react-app/issues/2374 + return; + } - window.addEventListener('load', () => { - const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + window.addEventListener("load", () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; - if (isLocalhost) { - // This is running on localhost. Let's check if a service worker still exists or not. - checkValidServiceWorker(swUrl, config); + if (isLocalhost) { + // This is running on localhost. Let's check if a service worker still exists or not. + checkValidServiceWorker(swUrl, config); - // Add some additional logging to localhost, pointing developers to the - // service worker/PWA documentation. - navigator.serviceWorker.ready.then(() => { - console.log( - 'This web app is being served cache-first by a service ' + - 'worker. To learn more, visit https://bit.ly/CRA-PWA' - ); + // Add some additional logging to localhost, pointing developers to the + // service worker/PWA documentation. + navigator.serviceWorker.ready.then(() => { + console.log( + "This web app is being served cache-first by a service " + + "worker. To learn more, visit https://bit.ly/CRA-PWA" + ); + }); + } else { + // Is not localhost. Just register service worker + registerValidSW(swUrl, config); + } }); - } else { - // Is not localhost. Just register service worker - registerValidSW(swUrl, config); - } - }); - } + } } function registerValidSW(swUrl, config) { - navigator.serviceWorker - .register(swUrl) - .then((registration) => { - registration.onupdatefound = () => { - const installingWorker = registration.installing; - if (installingWorker == null) { - return; - } - installingWorker.onstatechange = () => { - if (installingWorker.state === 'installed') { - if (navigator.serviceWorker.controller) { - // At this point, the updated precached content has been fetched, - // but the previous service worker will still serve the older - // content until all client tabs are closed. - console.log( - 'New content is available and will be used when all ' + - 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' - ); + navigator.serviceWorker + .register(swUrl) + .then((registration) => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + if (installingWorker == null) { + return; + } + installingWorker.onstatechange = () => { + if (installingWorker.state === "installed") { + if (navigator.serviceWorker.controller) { + // At this point, the updated precached content has been fetched, + // but the previous service worker will still serve the older + // content until all client tabs are closed. + console.log( + "New content is available and will be used when all " + + "tabs for this page are closed. See https://bit.ly/CRA-PWA." + ); - // Execute callback - if (config && config.onUpdate) { - config.onUpdate(registration); - } - } else { - // At this point, everything has been precached. - // It's the perfect time to display a - // "Content is cached for offline use." message. - console.log('Content is cached for offline use.'); + // Execute callback + if (config && config.onUpdate) { + config.onUpdate(registration); + } + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log("Content is cached for offline use."); - // Execute callback - if (config && config.onSuccess) { - config.onSuccess(registration); - } - } - } - }; - }; - }) - .catch((error) => { - console.error('Error during service worker registration:', error); - }); + // Execute callback + if (config && config.onSuccess) { + config.onSuccess(registration); + } + } + } + }; + }; + }) + .catch((error) => { + console.error("Error during service worker registration:", error); + }); } function checkValidServiceWorker(swUrl, config) { - // Check if the service worker can be found. If it can't reload the page. - fetch(swUrl, { - headers: { 'Service-Worker': 'script' }, - }) - .then((response) => { - // Ensure service worker exists, and that we really are getting a JS file. - const contentType = response.headers.get('content-type'); - if ( - response.status === 404 || - (contentType != null && contentType.indexOf('javascript') === -1) - ) { - // No service worker found. Probably a different app. Reload the page. - navigator.serviceWorker.ready.then((registration) => { - registration.unregister().then(() => { - window.location.reload(); - }); - }); - } else { - // Service worker found. Proceed as normal. - registerValidSW(swUrl, config); - } + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl, { + headers: {"Service-Worker": "script"}, }) - .catch(() => { - console.log( - 'No internet connection found. App is running in offline mode.' - ); - }); + .then((response) => { + // Ensure service worker exists, and that we really are getting a JS file. + const contentType = response.headers.get("content-type"); + if ( + response.status === 404 || + (contentType != null && contentType.indexOf("javascript") === -1) + ) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then((registration) => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl, config); + } + }) + .catch(() => { + console.log( + "No internet connection found. App is running in offline mode." + ); + }); } export function unregister() { - if ('serviceWorker' in navigator) { - navigator.serviceWorker.ready - .then((registration) => { - registration.unregister(); - }) - .catch((error) => { - console.error(error.message); - }); - } + if ("serviceWorker" in navigator) { + navigator.serviceWorker.ready + .then((registration) => { + registration.unregister(); + }) + .catch((error) => { + console.error(error.message); + }); + } } diff --git a/parsers/http_request_parser.go b/parsers/http_request_parser.go index bc98f8f..56093c9 100644 --- a/parsers/http_request_parser.go +++ b/parsers/http_request_parser.go @@ -82,7 +82,7 @@ func (p HttpRequestParser) TryParse(content []byte) Metadata { Trailer: JoinArrayMap(request.Trailer), Reproducers: HttpRequestMetadataReproducers{ CurlCommand: curlCommand(content), - RequestsCode: requestsCode(request), + RequestsCode: requestsCode(request, body), FetchRequest: fetchRequest(request, body), }, } @@ -99,19 +99,15 @@ func curlCommand(content []byte) string { } } -func requestsCode(request *http.Request) string { +func requestsCode(request *http.Request, body string) string { var b strings.Builder - var params string - if request.Form != nil { - params = toJson(JoinArrayMap(request.PostForm)) - } headers := toJson(JoinArrayMap(request.Header)) cookies := toJson(CookiesMap(request.Cookies())) b.WriteString("import requests\n\nresponse = requests." + strings.ToLower(request.Method) + "(") b.WriteString("\"" + request.URL.String() + "\"") - if params != "" { - b.WriteString(", data = " + params) + if body != "" { + b.WriteString(", data = \"" + strings.Replace(body, "\"", "\\\"", -1) + "\"") } if headers != "" { b.WriteString(", headers = " + headers) diff --git a/pcap_importer.go b/pcap_importer.go index 41ed082..1c80b3f 100644 --- a/pcap_importer.go +++ b/pcap_importer.go @@ -29,6 +29,7 @@ import ( "os" "path" "path/filepath" + "sort" "sync" "time" ) @@ -39,13 +40,14 @@ const initialAssemblerPoolSize = 16 const importUpdateProgressInterval = 100 * time.Millisecond type PcapImporter struct { - storage Storage - streamPool *tcpassembly.StreamPool - assemblers []*tcpassembly.Assembler - sessions map[string]ImportingSession - mAssemblers sync.Mutex - mSessions sync.Mutex - serverNet net.IPNet + storage Storage + streamPool *tcpassembly.StreamPool + assemblers []*tcpassembly.Assembler + sessions map[string]ImportingSession + mAssemblers sync.Mutex + mSessions sync.Mutex + serverNet net.IPNet + notificationController *NotificationController } type ImportingSession struct { @@ -63,7 +65,8 @@ type ImportingSession struct { type flowCount [2]int -func NewPcapImporter(storage Storage, serverNet net.IPNet, rulesManager RulesManager) *PcapImporter { +func NewPcapImporter(storage Storage, serverNet net.IPNet, rulesManager RulesManager, + notificationController *NotificationController) *PcapImporter { streamPool := tcpassembly.NewStreamPool(NewBiDirectionalStreamFactory(storage, serverNet, rulesManager)) var result []ImportingSession @@ -76,13 +79,14 @@ func NewPcapImporter(storage Storage, serverNet net.IPNet, rulesManager RulesMan } return &PcapImporter{ - storage: storage, - streamPool: streamPool, - assemblers: make([]*tcpassembly.Assembler, 0, initialAssemblerPoolSize), - sessions: sessions, - mAssemblers: sync.Mutex{}, - mSessions: sync.Mutex{}, - serverNet: serverNet, + storage: storage, + streamPool: streamPool, + assemblers: make([]*tcpassembly.Assembler, 0, initialAssemblerPoolSize), + sessions: sessions, + mAssemblers: sync.Mutex{}, + mSessions: sync.Mutex{}, + serverNet: serverNet, + notificationController: notificationController, } } @@ -136,6 +140,9 @@ func (pi *PcapImporter) GetSessions() []ImportingSession { for _, session := range pi.sessions { sessions = append(sessions, session) } + sort.Slice(sessions, func(i, j int) bool { + return sessions[i].StartedAt.Before(sessions[j].StartedAt) + }) pi.mSessions.Unlock() return sessions } @@ -202,6 +209,8 @@ func (pi *PcapImporter) parsePcap(session ImportingSession, fileName string, flu handle.Close() pi.releaseAssembler(assembler) pi.progressUpdate(session, fileName, true, "") + pi.notificationController.Notify("pcap.completed", session) + return } -- cgit v1.2.3-70-g09d2 From d429a344ef34bb9289616dc7ca11c161fabce5d7 Mon Sep 17 00:00:00 2001 From: Emiliano Ciavatta Date: Fri, 16 Oct 2020 15:26:55 +0200 Subject: Refactor js arrow functions --- frontend/src/components/App.js | 2 +- frontend/src/components/Notifications.js | 2 +- frontend/src/components/Timeline.js | 2 +- frontend/src/components/filters/AdvancedFilters.js | 4 ++-- .../src/components/filters/ExitSearchFilter.js | 2 +- .../components/filters/RulesConnectionsFilter.js | 2 +- frontend/src/components/objects/Connection.js | 4 ++-- .../components/objects/ConnectionMatchedRules.js | 4 ++-- frontend/src/components/panels/ConnectionsPane.js | 10 +++++----- frontend/src/components/panels/PcapsPane.js | 6 +++--- frontend/src/components/panels/RulesPane.js | 16 ++++++++-------- frontend/src/components/panels/SearchPane.js | 22 +++++++++++----------- frontend/src/components/panels/ServicesPane.js | 10 +++++----- frontend/src/components/panels/StreamsPane.js | 6 +++--- frontend/src/dispatcher.js | 8 ++++---- frontend/src/log.js | 2 +- parsers/http_request_parser.go | 3 +-- 17 files changed, 52 insertions(+), 53 deletions(-) (limited to 'frontend/src/components/objects/ConnectionMatchedRules.js') diff --git a/frontend/src/components/App.js b/frontend/src/components/App.js index 888ff86..e12a99d 100644 --- a/frontend/src/components/App.js +++ b/frontend/src/components/App.js @@ -27,7 +27,7 @@ class App extends Component { state = {}; componentDidMount() { - dispatcher.register("notifications", payload => { + dispatcher.register("notifications", (payload) => { if (payload.event === "connected") { this.setState({ connected: true, diff --git a/frontend/src/components/Notifications.js b/frontend/src/components/Notifications.js index a81eba1..0b47b43 100644 --- a/frontend/src/components/Notifications.js +++ b/frontend/src/components/Notifications.js @@ -107,7 +107,7 @@ class Notifications extends Component {
{ - this.state.closedNotifications.concat(this.state.notifications).map(n => { + this.state.closedNotifications.concat(this.state.notifications).map((n) => { const notificationClassnames = { "notification": true, "notification-closed": n.closed, diff --git a/frontend/src/components/Timeline.js b/frontend/src/components/Timeline.js index 94fa4d0..9ecbd80 100644 --- a/frontend/src/components/Timeline.js +++ b/frontend/src/components/Timeline.js @@ -127,7 +127,7 @@ class Timeline extends Component { const series = new TimeSeries({ name: "statistics", columns: ["time"].concat(columns), - points: zeroFilledMetrics.map((m) => [m["range_start"]].concat(columns.map(c => + points: zeroFilledMetrics.map((m) => [m["range_start"]].concat(columns.map((c) => ((metric in m) && (m[metric] != null)) ? (m[metric][c] || 0) : 0 ))) }); diff --git a/frontend/src/components/filters/AdvancedFilters.js b/frontend/src/components/filters/AdvancedFilters.js index 15667a5..8598185 100644 --- a/frontend/src/components/filters/AdvancedFilters.js +++ b/frontend/src/components/filters/AdvancedFilters.js @@ -28,10 +28,10 @@ class AdvancedFilters extends Component { componentDidMount() { this.urlParams = new URLSearchParams(this.props.location.search); - this.connectionsFiltersCallback = payload => { + this.connectionsFiltersCallback = (payload) => { this.urlParams = updateParams(this.urlParams, payload); const active = ["client_address", "client_port", "min_duration", "max_duration", "min_bytes", "max_bytes"] - .some(f => this.urlParams.has(f)); + .some((f) => this.urlParams.has(f)); if (this.state.active !== active) { this.setState({active}); } diff --git a/frontend/src/components/filters/ExitSearchFilter.js b/frontend/src/components/filters/ExitSearchFilter.js index 72cfb0c..0aacfd6 100644 --- a/frontend/src/components/filters/ExitSearchFilter.js +++ b/frontend/src/components/filters/ExitSearchFilter.js @@ -28,7 +28,7 @@ class ExitSearchFilter extends Component { let params = new URLSearchParams(this.props.location.search); this.setState({performedSearch: params.get("performed_search")}); - this.connectionsFiltersCallback = payload => { + this.connectionsFiltersCallback = (payload) => { if (this.state.performedSearch !== payload["performed_search"]) { this.setState({performedSearch: payload["performed_search"]}); } diff --git a/frontend/src/components/filters/RulesConnectionsFilter.js b/frontend/src/components/filters/RulesConnectionsFilter.js index 37d36b7..210ee36 100644 --- a/frontend/src/components/filters/RulesConnectionsFilter.js +++ b/frontend/src/components/filters/RulesConnectionsFilter.js @@ -37,7 +37,7 @@ class RulesConnectionsFilter extends Component { backend.get("/api/rules").then((res) => { let rules = res.json.flatMap((rule) => rule.enabled ? [{id: rule.id, name: rule.name}] : []); - activeRules = rules.filter((rule) => activeRules.some(id => rule.id === id)); + activeRules = rules.filter((rule) => activeRules.some((id) => rule.id === id)); this.setState({rules, activeRules}); }); diff --git a/frontend/src/components/objects/Connection.js b/frontend/src/components/objects/Connection.js index e5896e9..b70b7f7 100644 --- a/frontend/src/components/objects/Connection.js +++ b/frontend/src/components/objects/Connection.js @@ -37,7 +37,7 @@ class Connection extends Component { if (name === "hide") { const enabled = !this.props.data.hidden; backend.post(`/api/connections/${this.props.data.id}/${enabled ? "hide" : "show"}`) - .then(_ => { + .then((_) => { this.props.onEnabled(!enabled); this.setState({update: true}); }); @@ -45,7 +45,7 @@ class Connection extends Component { if (name === "mark") { const marked = this.props.data.marked; backend.post(`/api/connections/${this.props.data.id}/${marked ? "unmark" : "mark"}`) - .then(_ => { + .then((_) => { this.props.onMarked(!marked); this.setState({update: true}); }); diff --git a/frontend/src/components/objects/ConnectionMatchedRules.js b/frontend/src/components/objects/ConnectionMatchedRules.js index cfd1254..a69cad8 100644 --- a/frontend/src/components/objects/ConnectionMatchedRules.js +++ b/frontend/src/components/objects/ConnectionMatchedRules.js @@ -33,8 +33,8 @@ class ConnectionMatchedRules extends Component { }; render() { - const matchedRules = this.props.matchedRules.map(mr => { - const rule = this.props.rules.find(r => r.id === mr); + const matchedRules = this.props.matchedRules.map((mr) => { + const rule = this.props.rules.find((r) => r.id === mr); return this.onMatchedRulesSelected(rule.id)} name={rule.name} color={rule.color} small/>; }); diff --git a/frontend/src/components/panels/ConnectionsPane.js b/frontend/src/components/panels/ConnectionsPane.js index 9418fad..457c249 100644 --- a/frontend/src/components/panels/ConnectionsPane.js +++ b/frontend/src/components/panels/ConnectionsPane.js @@ -61,8 +61,8 @@ class ConnectionsPane extends Component { const id = match[1]; additionalParams.from = id; backend.get(`/api/connections/${id}`) - .then(res => this.connectionSelected(res.json)) - .catch(error => log.error("Error loading initial connection", error)); + .then((res) => this.connectionSelected(res.json)) + .catch((error) => log.error("Error loading initial connection", error)); } this.loadConnections(additionalParams, urlParams, true).then(() => log.debug("Connections loaded")); @@ -283,11 +283,11 @@ class ConnectionsPane extends Component { { - this.state.connections.flatMap(c => { + this.state.connections.flatMap((c) => { return [ this.connectionSelected(c)} selected={this.state.selected === c.id} - onMarked={marked => c.marked = marked} - onEnabled={enabled => c.hidden = !enabled} + onMarked={(marked) => c.marked = marked} + onEnabled={(enabled) => c.hidden = !enabled} services={this.state.services}/>, c.matched_rules.length > 0 && { this.setState({ processStatusCode: res.status, diff --git a/frontend/src/components/panels/RulesPane.js b/frontend/src/components/panels/RulesPane.js index 0bbd407..cdfe185 100644 --- a/frontend/src/components/panels/RulesPane.js +++ b/frontend/src/components/panels/RulesPane.js @@ -103,17 +103,17 @@ class RulesPane extends Component { }; loadRules = () => { - backend.get("/api/rules").then(res => this.setState({rules: res.json, rulesStatusCode: res.status})) - .catch(res => this.setState({rulesStatusCode: res.status, rulesResponse: JSON.stringify(res.json)})); + backend.get("/api/rules").then((res) => this.setState({rules: res.json, rulesStatusCode: res.status})) + .catch((res) => this.setState({rulesStatusCode: res.status, rulesResponse: JSON.stringify(res.json)})); }; addRule = () => { if (this.validateRule(this.state.newRule)) { - backend.post("/api/rules", this.state.newRule).then(res => { + backend.post("/api/rules", this.state.newRule).then((res) => { this.reset(); this.setState({ruleStatusCode: res.status}); this.loadRules(); - }).catch(res => { + }).catch((res) => { this.setState({ruleStatusCode: res.status, ruleResponse: JSON.stringify(res.json)}); }); } @@ -122,11 +122,11 @@ class RulesPane extends Component { updateRule = () => { const rule = this.state.selectedRule; if (this.validateRule(rule)) { - backend.put(`/api/rules/${rule.id}`, rule).then(res => { + backend.put(`/api/rules/${rule.id}`, rule).then((res) => { this.reset(); this.setState({ruleStatusCode: res.status}); this.loadRules(); - }).catch(res => { + }).catch((res) => { this.setState({ruleStatusCode: res.status, ruleResponse: JSON.stringify(res.json)}); }); } @@ -247,7 +247,7 @@ class RulesPane extends Component { const rule = this.currentRule(); const pattern = this.state.selectedPattern || this.state.newPattern; - let rules = this.state.rules.map(r => + let rules = this.state.rules.map((r) => { this.reset(); this.setState({selectedRule: _.cloneDeep(r)}); @@ -262,7 +262,7 @@ class RulesPane extends Component { let patterns = (this.state.selectedPattern == null && !isUpdate ? rule.patterns.concat(this.state.newPattern) : rule.patterns - ).map(p => p === pattern ? + ).map((p) => p === pattern ?
- { + { return {name: t}; })} name="terms" min={3} inline allowNew={true} readonly={regexOptionsModified || options["text_search"]["exact_phrase"]} - onChange={(tags) => this.updateParam(s => s["text_search"].terms = tags.map(t => t.name))}/> + onChange={(tags) => this.updateParam((s) => s["text_search"].terms = tags.map(t => t.name))}/> { return {name: t}; })} name="excluded_terms" min={3} inline allowNew={true} readonly={regexOptionsModified || options["text_search"]["exact_phrase"]} - onChange={(tags) => this.updateParam(s => s["text_search"]["excluded_terms"] = tags.map(t => t.name))}/> + onChange={(tags) => this.updateParam((s) => s["text_search"]["excluded_terms"] = tags.map(t => t.name))}/> or this.updateParam(s => s["text_search"]["exact_phrase"] = v)} + onChange={(v) => this.updateParam((s) => s["text_search"]["exact_phrase"] = v)} readonly={regexOptionsModified || (Array.isArray(options["text_search"].terms) && options["text_search"].terms.length > 0)}/> this.updateParam(s => s["text_search"]["case_sensitive"] = v)}/> + onChange={(v) => this.updateParam((s) => s["text_search"]["case_sensitive"] = v)}/>
@@ -264,28 +264,28 @@ class SearchPane extends Component { this.updateParam(s => s["regex_search"].pattern = v)}/> + onChange={(v) => this.updateParam((s) => s["regex_search"].pattern = v)}/> or this.updateParam(s => s["regex_search"]["not_pattern"] = v)}/> + onChange={(v) => this.updateParam((s) => s["regex_search"]["not_pattern"] = v)}/>
this.updateParam(s => s["regex_search"]["case_insensitive"] = v)}/> + onChange={(v) => this.updateParam((s) => s["regex_search"]["case_insensitive"] = v)}/> this.updateParam(s => s["regex_search"]["multi_line"] = v)}/> + onChange={(v) => this.updateParam((s) => s["regex_search"]["multi_line"] = v)}/> this.updateParam(s => s["regex_search"]["ignore_whitespaces"] = v)}/> + onChange={(v) => this.updateParam((s) => s["regex_search"]["ignore_whitespaces"] = v)}/> this.updateParam(s => s["regex_search"]["dot_character"] = v)}/> + onChange={(v) => this.updateParam((s) => s["regex_search"]["dot_character"] = v)}/>
diff --git a/frontend/src/components/panels/ServicesPane.js b/frontend/src/components/panels/ServicesPane.js index 48d9e29..5986804 100644 --- a/frontend/src/components/panels/ServicesPane.js +++ b/frontend/src/components/panels/ServicesPane.js @@ -68,18 +68,18 @@ class ServicesPane extends Component { loadServices = () => { backend.get("/api/services") - .then(res => this.setState({services: Object.values(res.json), servicesStatusCode: res.status})) - .catch(res => this.setState({servicesStatusCode: res.status, servicesResponse: JSON.stringify(res.json)})); + .then((res) => this.setState({services: Object.values(res.json), servicesStatusCode: res.status})) + .catch((res) => this.setState({servicesStatusCode: res.status, servicesResponse: JSON.stringify(res.json)})); }; updateService = () => { const service = this.state.currentService; if (this.validateService(service)) { - backend.put("/api/services", service).then(res => { + backend.put("/api/services", service).then((res) => { this.reset(); this.setState({serviceStatusCode: res.status}); this.loadServices(); - }).catch(res => { + }).catch((res) => { this.setState({serviceStatusCode: res.status, serviceResponse: JSON.stringify(res.json)}); }); } @@ -126,7 +126,7 @@ class ServicesPane extends Component { const isUpdate = this.state.isUpdate; const service = this.state.currentService; - let services = this.state.services.map(s => + let services = this.state.services.map((s) => { this.reset(); this.setState({isUpdate: true, currentService: _.cloneDeep(s)}); diff --git a/frontend/src/components/panels/StreamsPane.js b/frontend/src/components/panels/StreamsPane.js index 1819fec..9470d7d 100644 --- a/frontend/src/components/panels/StreamsPane.js +++ b/frontend/src/components/panels/StreamsPane.js @@ -67,7 +67,7 @@ class StreamsPane extends Component { loadStream = (connectionId) => { this.setState({messages: [], currentId: connectionId}); backend.get(`/api/streams/${connectionId}?format=${this.state.format}`) - .then(res => this.setState({messages: res.json})); + .then((res) => this.setState({messages: res.json})); }; setFormat = (format) => { @@ -166,8 +166,8 @@ class StreamsPane extends Component { downloadStreamRaw = (value) => { if (this.state.currentId) { backend.download(`/api/streams/${this.props.connection.id}/download?format=${this.state.format}&type=${value}`) - .then(res => downloadBlob(res.blob, `${this.state.currentId}-${value}-${this.state.format}.txt`)) - .catch(_ => log.error("Failed to download stream messages")); + .then((res) => downloadBlob(res.blob, `${this.state.currentId}-${value}-${this.state.format}.txt`)) + .catch((_) => log.error("Failed to download stream messages")); } }; diff --git a/frontend/src/dispatcher.js b/frontend/src/dispatcher.js index ef5dbde..32f3f33 100644 --- a/frontend/src/dispatcher.js +++ b/frontend/src/dispatcher.js @@ -24,7 +24,7 @@ class Dispatcher { } dispatch = (topic, payload) => { - this.listeners.filter(l => l.topic === topic).forEach(l => l.callback(payload)); + this.listeners.filter((l) => l.topic === topic).forEach((l) => l.callback(payload)); }; register = (topic, callback) => { @@ -34,20 +34,20 @@ class Dispatcher { if (typeof topic === "string") { this.listeners.push({topic, callback}); } else if (typeof topic === "object" && Array.isArray(topic)) { - topic.forEach(e => { + topic.forEach((e) => { if (typeof e !== "string") { throw new Error("all topics must be strings"); } }); - topic.forEach(e => this.listeners.push({e, callback})); + topic.forEach((e) => this.listeners.push({e, callback})); } else { throw new Error("topic must be a string or an array of strings"); } }; unregister = (callback) => { - _.remove(this.listeners, l => l.callback === callback); + _.remove(this.listeners, (l) => l.callback === callback); }; } diff --git a/frontend/src/log.js b/frontend/src/log.js index 0afac47..9a75fbc 100644 --- a/frontend/src/log.js +++ b/frontend/src/log.js @@ -23,7 +23,7 @@ const log = { }; function isDevelopment() { - return !process.env.NODE_ENV || process.env.NODE_ENV === 'development'; + return !process.env.NODE_ENV || process.env.NODE_ENV === "development"; } export default log; diff --git a/parsers/http_request_parser.go b/parsers/http_request_parser.go index d7ed81c..98ba8e3 100644 --- a/parsers/http_request_parser.go +++ b/parsers/http_request_parser.go @@ -148,9 +148,8 @@ func fetchRequest(request *http.Request, body string) string { if jsonData := toJSON(data); jsonData != "" { return "fetch(\"" + request.URL.String() + "\", " + jsonData + ");" - } else { - return "invalid-request" } + return "invalid-request" } func toJSON(obj interface{}) string { -- cgit v1.2.3-70-g09d2