Started at {startedAt.toLocaleDateString() + " " + startedAt.toLocaleTimeString()}
Processed at {processedAt.toLocaleDateString() + " " + processedAt.toLocaleTimeString()}
@@ -88,28 +89,20 @@ class Connection extends Component {
this.props.addServicePortFilter(conn.port_dst)}/>
+ onClick={() => this.props.addServicePortFilter(conn["port_dst"])}/>
|
-
{conn.ip_src} |
-
{conn.port_src} |
-
{conn.ip_dst} |
-
{conn.port_dst} |
-
{dateTimeToTime(conn.started_at)} |
+
{conn["ip_src"]} |
+
{conn["port_src"]} |
+
{conn["ip_dst"]} |
+
{conn["port_dst"]} |
-
- {durationBetween(startedAt, closedAt)}
-
+
|
-
{formatSize(conn.client_bytes)} |
-
{formatSize(conn.server_bytes)} |
+
{durationBetween(startedAt, closedAt)} |
+
{formatSize(conn["client_bytes"])} |
+
{formatSize(conn["server_bytes"])} |
- {/*Hide this connection from the list)}>*/}
- {/* this.handleAction("hide")}>%*/}
- {/**/}
Mark this connection)}>
{
const notifications = this.state.notifications;
notification.open = true;
this.setState({notifications});
}, 100);
- setTimeout(() => {
+ const hideHandle = setTimeout(() => {
const notifications = _.without(this.state.notifications, notification);
const closedNotifications = this.state.closedNotifications.concat([notification]);
notification.closed = true;
this.setState({notifications, closedNotifications});
}, 5000);
- setTimeout(() => {
+ const removeHandle = setTimeout(() => {
const closedNotifications = _.without(this.state.closedNotifications, notification);
this.setState({closedNotifications});
}, 6000);
+
+ notification.onClick = () => {
+ clearTimeout(hideHandle);
+ clearTimeout(removeHandle);
+ const notifications = _.without(this.state.notifications, notification);
+ this.setState({notifications});
+ };
});
}
@@ -45,7 +51,7 @@ class Notifications extends Component {
{
this.state.closedNotifications.concat(this.state.notifications).map(n =>
+ {"notification-open": n.open})} onClick={n.onClick}>
{n.event}
{JSON.stringify(n.message)}
diff --git a/frontend/src/components/Notifications.scss b/frontend/src/components/Notifications.scss
index bec7734..324d0bb 100644
--- a/frontend/src/components/Notifications.scss
+++ b/frontend/src/components/Notifications.scss
@@ -18,6 +18,7 @@
color: $color-green-light;
border-left: 5px solid $color-green-dark;
background-color: $color-green;
+ cursor: pointer;
.notification-title {
font-size: 0.9em;
diff --git a/frontend/src/views/App.js b/frontend/src/views/App.js
index c14b7f5..4bb9f57 100644
--- a/frontend/src/views/App.js
+++ b/frontend/src/views/App.js
@@ -2,7 +2,7 @@ import React, {Component} from 'react';
import './App.scss';
import Header from "./Header";
import MainPane from "../components/panels/MainPane";
-import Footer from "./Footer";
+import Timeline from "./Timeline";
import {BrowserRouter as Router} from "react-router-dom";
import Filters from "./Filters";
import ConfigurationPane from "../components/panels/ConfigurationPane";
@@ -52,7 +52,7 @@ class App extends Component {
{modal}
- {this.state.configured && }
+ {this.state.configured && }
}
diff --git a/frontend/src/views/Connections.js b/frontend/src/views/Connections.js
index bd631a2..e835dcb 100644
--- a/frontend/src/views/Connections.js
+++ b/frontend/src/views/Connections.js
@@ -17,7 +17,6 @@ class Connections extends Component {
connections: [],
firstConnection: null,
lastConnection: null,
- queryString: null
};
constructor(props) {
@@ -29,14 +28,15 @@ class Connections extends Component {
this.queryLimit = 50;
this.connectionsListRef = React.createRef();
this.lastScrollPosition = 0;
+ this.doQueryStringRedirect = false;
+ this.doSelectedConnectionRedirect = false;
}
componentDidMount() {
this.loadConnections({limit: this.queryLimit})
.then(() => this.setState({loaded: true}));
- if (this.props.initialConnection != null) {
+ if (this.props.initialConnection) {
this.setState({selected: this.props.initialConnection.id});
- // TODO: scroll to initial connection
}
dispatcher.register("timeline_updates", payload => {
@@ -62,19 +62,24 @@ class Connections extends Component {
}
connectionSelected = (c) => {
+ this.doSelectedConnectionRedirect = true;
this.setState({selected: c.id});
this.props.onSelected(c);
};
componentDidUpdate(prevProps, prevState, snapshot) {
if (this.state.loaded && prevProps.location.search !== this.props.location.search) {
- this.setState({queryString: this.props.location.search});
this.loadConnections({limit: this.queryLimit})
.then(() => log.info("Connections reloaded after query string update"));
}
}
handleScroll = (e) => {
+ if (this.disableScrollHandler) {
+ this.lastScrollPosition = e.currentTarget.scrollTop;
+ return;
+ }
+
let relativeScroll = e.currentTarget.scrollTop / (e.currentTarget.scrollHeight - e.currentTarget.clientHeight);
if (!this.state.loading && relativeScroll > this.scrollBottomThreashold) {
this.loadConnections({from: this.state.lastConnection.id, limit: this.queryLimit,})
@@ -103,7 +108,8 @@ class Connections extends Component {
addServicePortFilter = (port) => {
const urlParams = new URLSearchParams(this.props.location.search);
urlParams.set("service_port", port);
- this.setState({queryString: "?" + urlParams});
+ this.doQueryStringRedirect = true;
+ this.setState({queryString: urlParams});
};
addMatchedRulesFilter = (matchedRule) => {
@@ -112,7 +118,8 @@ class Connections extends Component {
if (!oldMatchedRules.includes(matchedRule)) {
urlParams.append("matched_rules", matchedRule);
- this.setState({queryString: "?" + urlParams});
+ this.doQueryStringRedirect = true;
+ this.setState({queryString: urlParams});
}
};
@@ -139,7 +146,7 @@ class Connections extends Component {
if (params !== undefined && params.from !== undefined && params.to === undefined) {
if (res.length > 0) {
- connections = this.state.connections.concat(res);
+ connections = this.state.connections.concat(res.slice(1));
lastConnection = connections[connections.length - 1];
if (connections.length > this.maxConnections) {
connections = connections.slice(connections.length - this.maxConnections,
@@ -149,7 +156,7 @@ class Connections extends Component {
}
} else if (params !== undefined && params.to !== undefined && params.from === undefined) {
if (res.length > 0) {
- connections = res.concat(this.state.connections);
+ connections = res.slice(0, res.length - 1).concat(this.state.connections);
firstConnection = connections[0];
if (connections.length > this.maxConnections) {
connections = connections.slice(0, this.maxConnections);
@@ -157,7 +164,6 @@ class Connections extends Component {
}
}
} else {
- this.connectionsListRef.current.scrollTop = 0;
if (res.length > 0) {
connections = res;
firstConnection = connections[0];
@@ -194,9 +200,12 @@ class Connections extends Component {
render() {
let redirect;
- let queryString = this.state.queryString !== null ? this.state.queryString : "";
- if (this.state.selected) {
- redirect = ;
+ if (this.doSelectedConnectionRedirect) {
+ redirect = ;
+ this.doSelectedConnectionRedirect = false;
+ } else if (this.doQueryStringRedirect) {
+ redirect = ;
+ this.doQueryStringRedirect = false;
}
let loading = null;
@@ -209,10 +218,15 @@ class Connections extends Component {
return (
{this.state.showMoreRecentButton &&
-
+ {
+ this.disableScrollHandler = true;
+ this.connectionsListRef.current.scrollTop = 0;
this.loadConnections({limit: this.queryLimit})
- .then(() => log.info("Most recent connections loaded"))
- }/>
+ .then(() => {
+ this.disableScrollHandler = false;
+ log.info("Most recent connections loaded");
+ });
+ }}/>
}
diff --git a/frontend/src/views/Footer.js b/frontend/src/views/Footer.js
deleted file mode 100644
index dcf9cf8..0000000
--- a/frontend/src/views/Footer.js
+++ /dev/null
@@ -1,176 +0,0 @@
-import React, {Component} from 'react';
-import './Footer.scss';
-import {
- ChartContainer,
- ChartRow,
- Charts,
- LineChart,
- MultiBrush,
- Resizable,
- styler,
- YAxis
-} from "react-timeseries-charts";
-import {TimeRange, TimeSeries} from "pondjs";
-import backend from "../backend";
-import ChoiceField from "../components/fields/ChoiceField";
-import {withRouter} from "react-router-dom";
-import log from "../log";
-import dispatcher from "../dispatcher";
-
-
-class Footer extends Component {
-
- state = {
- metric: "connections_per_service"
- };
-
- constructor() {
- super();
-
- this.disableTimeSeriesChanges = false;
- this.selectionTimeout = null;
- }
-
- filteredPort = () => {
- const urlParams = new URLSearchParams(this.props.location.search);
- 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"));
-
- dispatcher.register("connection_updates", payload => {
- this.setState({
- selection: new TimeRange(payload.from, payload.to),
- });
- });
- }
-
- 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"));
- }
- }
-
- loadStatistics = async (metric, filteredPort) => {
- const urlParams = new URLSearchParams();
- urlParams.set("metric", metric);
-
- let services = (await backend.get("/api/services")).json;
- if (filteredPort && services[filteredPort]) {
- const service = services[filteredPort];
- services = {};
- services[filteredPort] = service;
- }
-
- const ports = Object.keys(services);
- ports.forEach(s => urlParams.append("ports", s));
-
- const metrics = (await backend.get("/api/statistics?" + urlParams)).json;
- const series = new TimeSeries({
- name: "statistics",
- columns: ["time"].concat(ports),
- points: metrics.map(m => [new Date(m["range_start"])].concat(ports.map(p => m[metric][p] || 0)))
- });
- this.setState({
- metric,
- series,
- services,
- timeRange: series.range(),
- });
- log.debug(`Loaded statistics for metric "${metric}" for services [${ports}]`);
- };
-
- createStyler = () => {
- return styler(Object.keys(this.state.services).map(port => {
- return {key: port, color: this.state.services[port].color, width: 2};
- }));
- };
-
- handleTimeRangeChange = (timeRange) => {
- if (!this.disableTimeSeriesChanges) {
- this.setState({timeRange});
- }
- };
-
- handleSelectionChange = (timeRange) => {
- this.disableTimeSeriesChanges = true;
-
- this.setState({selection: timeRange});
- if (this.selectionTimeout) {
- clearTimeout(this.selectionTimeout);
- }
- this.selectionTimeout = setTimeout(() => {
- dispatcher.dispatch("timeline_updates", {
- from: timeRange.begin(),
- to: timeRange.end()
- });
- this.selectionTimeout = null;
- this.disableTimeSeriesChanges = false;
- }, 1000);
- };
-
- aggregateSeries = (func) => {
- const values = this.state.series.columns().map(c => this.state.series[func](c));
- return Math[func](...values);
- };
-
- render() {
- return (
-
- );
- }
-}
-
-export default withRouter(Footer);
diff --git a/frontend/src/views/Footer.scss b/frontend/src/views/Footer.scss
deleted file mode 100644
index 14360d4..0000000
--- a/frontend/src/views/Footer.scss
+++ /dev/null
@@ -1,22 +0,0 @@
-@import "../colors.scss";
-
-.footer {
- padding: 15px;
-
- .time-line {
- position: relative;
- background-color: $color-primary-0;
-
- .metric-selection {
- font-size: 0.8em;
- position: absolute;
- top: 5px;
- right: 10px;
- }
- }
-
- svg text {
- font-family: "Fira Code", monospace !important;
- fill: $color-primary-4 !important;
- }
-}
diff --git a/frontend/src/views/Header.js b/frontend/src/views/Header.js
index 944f1d5..f5eff17 100644
--- a/frontend/src/views/Header.js
+++ b/frontend/src/views/Header.js
@@ -2,7 +2,7 @@ import React, {Component} from 'react';
import Typed from 'typed.js';
import './Header.scss';
import {filtersDefinitions, filtersNames} from "../components/filters/FiltersDefinitions";
-import {Link} from "react-router-dom";
+import {Link, withRouter} from "react-router-dom";
import ButtonField from "../components/fields/ButtonField";
class Header extends Component {
@@ -46,7 +46,7 @@ class Header extends Component {
render() {
let quickFilters = filtersNames.filter(name => this.state[`${name}_active`])
- .map(name => {filtersDefinitions[name]})
+ .map(name => {filtersDefinitions[name]})
.slice(0, 5);
return (
@@ -68,18 +68,18 @@ class Header extends Component {
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
@@ -89,4 +89,4 @@ class Header extends Component {
}
}
-export default Header;
+export default withRouter(Header);
diff --git a/frontend/src/views/Timeline.js b/frontend/src/views/Timeline.js
new file mode 100644
index 0000000..3adbf88
--- /dev/null
+++ b/frontend/src/views/Timeline.js
@@ -0,0 +1,215 @@
+import React, {Component} from 'react';
+import './Timeline.scss';
+import {
+ ChartContainer,
+ ChartRow,
+ Charts,
+ LineChart,
+ MultiBrush,
+ Resizable,
+ styler,
+ YAxis
+} from "react-timeseries-charts";
+import {TimeRange, TimeSeries} from "pondjs";
+import backend from "../backend";
+import ChoiceField from "../components/fields/ChoiceField";
+import {withRouter} from "react-router-dom";
+import log from "../log";
+import dispatcher from "../dispatcher";
+
+const minutes = 60 * 1000;
+
+class Timeline extends Component {
+
+ state = {
+ metric: "connections_per_service"
+ };
+
+ constructor() {
+ super();
+
+ this.disableTimeSeriesChanges = false;
+ this.selectionTimeout = null;
+ }
+
+ filteredPort = () => {
+ const urlParams = new URLSearchParams(this.props.location.search);
+ 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"));
+
+ dispatcher.register("connection_updates", payload => {
+ this.setState({
+ selection: new TimeRange(payload.from, payload.to),
+ });
+ });
+
+ dispatcher.register("notifications", payload => {
+ if (payload.event === "services.edit") {
+ this.loadServices().then(() => log.debug("Services reloaded after notification update"));
+ }
+ });
+ }
+
+ 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"));
+ }
+ }
+
+ loadStatistics = async (metric, filteredPort) => {
+ 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;
+ }
+
+ const ports = Object.keys(services);
+ ports.forEach(s => urlParams.append("ports", s));
+
+ const metrics = (await backend.get("/api/statistics?" + urlParams)).json;
+ const zeroFilledMetrics = [];
+ const toTime = m => new Date(m["range_start"]).getTime();
+
+ if (metrics.length > 0) {
+ let i = 0;
+ for (let interval = toTime(metrics[0]); interval <= toTime(metrics[metrics.length - 1]); interval += minutes) {
+ if (interval === toTime(metrics[i])) {
+ const m = metrics[i++];
+ m["range_start"] = new Date(m["range_start"]);
+ zeroFilledMetrics.push(m);
+ } else {
+ const m = {};
+ m["range_start"] = new Date(interval);
+ m[metric] = {};
+ ports.forEach(p => m[metric][p] = 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)))
+ });
+ 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),
+ start,
+ end
+ });
+ log.debug(`Loaded statistics for metric "${metric}" for services [${ports}]`);
+ };
+
+ loadServices = async () => {
+ const services = (await backend.get("/api/services")).json;
+ this.setState({services});
+ return services;
+ };
+
+ createStyler = () => {
+ return styler(Object.keys(this.state.services).map(port => {
+ return {key: port, color: this.state.services[port].color, width: 2};
+ }));
+ };
+
+ handleTimeRangeChange = (timeRange) => {
+ if (!this.disableTimeSeriesChanges) {
+ this.setState({timeRange});
+ }
+ };
+
+ handleSelectionChange = (timeRange) => {
+ this.disableTimeSeriesChanges = true;
+
+ this.setState({selection: timeRange});
+ if (this.selectionTimeout) {
+ clearTimeout(this.selectionTimeout);
+ }
+ this.selectionTimeout = setTimeout(() => {
+ dispatcher.dispatch("timeline_updates", {
+ from: timeRange.begin(),
+ to: timeRange.end()
+ });
+ this.selectionTimeout = null;
+ this.disableTimeSeriesChanges = false;
+ }, 1000);
+ };
+
+ aggregateSeries = (func) => {
+ const values = this.state.series.columns().map(c => this.state.series[func](c));
+ return Math[func](...values);
+ };
+
+ render() {
+ if (!this.state.series) {
+ return null;
+ }
+
+ return (
+
+ );
+ }
+}
+
+export default withRouter(Timeline);
diff --git a/frontend/src/views/Timeline.scss b/frontend/src/views/Timeline.scss
new file mode 100644
index 0000000..14360d4
--- /dev/null
+++ b/frontend/src/views/Timeline.scss
@@ -0,0 +1,22 @@
+@import "../colors.scss";
+
+.footer {
+ padding: 15px;
+
+ .time-line {
+ position: relative;
+ background-color: $color-primary-0;
+
+ .metric-selection {
+ font-size: 0.8em;
+ position: absolute;
+ top: 5px;
+ right: 10px;
+ }
+ }
+
+ svg text {
+ font-family: "Fira Code", monospace !important;
+ fill: $color-primary-4 !important;
+ }
+}
--
cgit v1.2.3-70-g09d2
From a30815021e61023f996b1450ddcd9164a6e18bef Mon Sep 17 00:00:00 2001
From: Emiliano Ciavatta
Date: Thu, 8 Oct 2020 17:07:07 +0200
Subject: Add header license to all files
---
application_context.go | 17 +++++++++++++++++
application_context_test.go | 17 +++++++++++++++++
application_router.go | 17 +++++++++++++++++
application_router_test.go | 17 +++++++++++++++++
caronte.go | 17 +++++++++++++++++
caronte_test.go | 17 +++++++++++++++++
connection_handler.go | 17 +++++++++++++++++
connection_handler_test.go | 17 +++++++++++++++++
connection_streams_controller.go | 17 +++++++++++++++++
connections_controller.go | 17 +++++++++++++++++
frontend/src/backend.js | 17 +++++++++++++++++
frontend/src/components/Connection.js | 17 +++++++++++++++++
frontend/src/components/ConnectionContent.js | 17 +++++++++++++++++
frontend/src/components/ConnectionMatchedRules.js | 17 +++++++++++++++++
frontend/src/components/MessageAction.js | 17 +++++++++++++++++
frontend/src/components/Notifications.js | 17 +++++++++++++++++
frontend/src/components/fields/ButtonField.js | 17 +++++++++++++++++
frontend/src/components/fields/CheckField.js | 17 +++++++++++++++++
frontend/src/components/fields/ChoiceField.js | 17 +++++++++++++++++
frontend/src/components/fields/InputField.js | 17 +++++++++++++++++
frontend/src/components/fields/TextField.js | 17 +++++++++++++++++
.../src/components/fields/extensions/ColorField.js | 17 +++++++++++++++++
.../components/fields/extensions/NumericField.js | 17 +++++++++++++++++
.../components/filters/BooleanConnectionsFilter.js | 17 +++++++++++++++++
.../src/components/filters/FiltersDefinitions.js | 17 +++++++++++++++++
.../components/filters/RulesConnectionsFilter.js | 17 +++++++++++++++++
.../components/filters/StringConnectionsFilter.js | 17 +++++++++++++++++
frontend/src/components/objects/LinkPopover.js | 17 +++++++++++++++++
frontend/src/components/panels/ConfigurationPane.js | 17 +++++++++++++++++
frontend/src/components/panels/MainPane.js | 17 +++++++++++++++++
frontend/src/components/panels/PcapPane.js | 17 +++++++++++++++++
frontend/src/components/panels/RulePane.js | 17 +++++++++++++++++
frontend/src/components/panels/ServicePane.js | 17 +++++++++++++++++
frontend/src/dispatcher.js | 16 ++++++++++++++++
frontend/src/index.js | 17 +++++++++++++++++
frontend/src/log.js | 17 +++++++++++++++++
frontend/src/notifications.js | 17 +++++++++++++++++
frontend/src/setupProxy.js | 17 +++++++++++++++++
frontend/src/utils.js | 17 +++++++++++++++++
frontend/src/validation.js | 16 ++++++++++++++++
frontend/src/views/App.js | 17 +++++++++++++++++
frontend/src/views/Connections.js | 17 +++++++++++++++++
frontend/src/views/Filters.js | 21 +++++++++++++++++++--
frontend/src/views/Header.js | 17 +++++++++++++++++
frontend/src/views/Timeline.js | 17 +++++++++++++++++
notification_controller.go | 17 +++++++++++++++++
parsers/http_request_parser.go | 17 +++++++++++++++++
parsers/http_response_parser.go | 17 +++++++++++++++++
parsers/parser.go | 17 +++++++++++++++++
parsers/parser_utils.go | 17 +++++++++++++++++
pcap_importer.go | 17 +++++++++++++++++
pcap_importer_test.go | 17 +++++++++++++++++
resources_controller.go | 17 +++++++++++++++++
rules_manager.go | 17 +++++++++++++++++
rules_manager_test.go | 17 +++++++++++++++++
services_controller.go | 17 +++++++++++++++++
statistics_controller.go | 17 +++++++++++++++++
storage.go | 17 +++++++++++++++++
storage_test.go | 17 +++++++++++++++++
stream_handler.go | 17 +++++++++++++++++
stream_handler_test.go | 17 +++++++++++++++++
utils.go | 17 +++++++++++++++++
62 files changed, 1054 insertions(+), 2 deletions(-)
(limited to 'frontend/src/views')
diff --git a/application_context.go b/application_context.go
index 9a9c97a..9897bb6 100644
--- a/application_context.go
+++ b/application_context.go
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package main
import (
diff --git a/application_context_test.go b/application_context_test.go
index 28c81a5..a7f1a49 100644
--- a/application_context_test.go
+++ b/application_context_test.go
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package main
import (
diff --git a/application_router.go b/application_router.go
index da71538..9fd7e3d 100644
--- a/application_router.go
+++ b/application_router.go
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package main
import (
diff --git a/application_router_test.go b/application_router_test.go
index f4804e3..9741eed 100644
--- a/application_router_test.go
+++ b/application_router_test.go
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package main
import (
diff --git a/caronte.go b/caronte.go
index d999724..d4265bc 100644
--- a/caronte.go
+++ b/caronte.go
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package main
import (
diff --git a/caronte_test.go b/caronte_test.go
index 12ec50f..8935ea3 100644
--- a/caronte_test.go
+++ b/caronte_test.go
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package main
import (
diff --git a/connection_handler.go b/connection_handler.go
index 3d38531..6b2b411 100644
--- a/connection_handler.go
+++ b/connection_handler.go
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package main
import (
diff --git a/connection_handler_test.go b/connection_handler_test.go
index 0bee0ac..d980041 100644
--- a/connection_handler_test.go
+++ b/connection_handler_test.go
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package main
import (
diff --git a/connection_streams_controller.go b/connection_streams_controller.go
index 98f2aca..9251a3a 100644
--- a/connection_streams_controller.go
+++ b/connection_streams_controller.go
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package main
import (
diff --git a/connections_controller.go b/connections_controller.go
index e872c9f..30a5ee5 100644
--- a/connections_controller.go
+++ b/connections_controller.go
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package main
import (
diff --git a/frontend/src/backend.js b/frontend/src/backend.js
index 1b2d8d2..cc8604a 100644
--- a/frontend/src/backend.js
+++ b/frontend/src/backend.js
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
async function json(method, url, data, json, headers) {
const options = {
method: method,
diff --git a/frontend/src/components/Connection.js b/frontend/src/components/Connection.js
index b7e2531..c7b0010 100644
--- a/frontend/src/components/Connection.js
+++ b/frontend/src/components/Connection.js
@@ -1,3 +1,20 @@
+/*
+ * 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 './Connection.scss';
import {Form, OverlayTrigger, Popover} from "react-bootstrap";
diff --git a/frontend/src/components/ConnectionContent.js b/frontend/src/components/ConnectionContent.js
index b09dcf3..b468277 100644
--- a/frontend/src/components/ConnectionContent.js
+++ b/frontend/src/components/ConnectionContent.js
@@ -1,3 +1,20 @@
+/*
+ * 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 './ConnectionContent.scss';
import {Row} from 'react-bootstrap';
diff --git a/frontend/src/components/ConnectionMatchedRules.js b/frontend/src/components/ConnectionMatchedRules.js
index 21f2a92..35643c5 100644
--- a/frontend/src/components/ConnectionMatchedRules.js
+++ b/frontend/src/components/ConnectionMatchedRules.js
@@ -1,3 +1,20 @@
+/*
+ * 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";
diff --git a/frontend/src/components/MessageAction.js b/frontend/src/components/MessageAction.js
index 8f4b031..b94cbb9 100644
--- a/frontend/src/components/MessageAction.js
+++ b/frontend/src/components/MessageAction.js
@@ -1,3 +1,20 @@
+/*
+ * 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 './MessageAction.scss';
import {Modal} from "react-bootstrap";
diff --git a/frontend/src/components/Notifications.js b/frontend/src/components/Notifications.js
index 9ce2b58..1017a42 100644
--- a/frontend/src/components/Notifications.js
+++ b/frontend/src/components/Notifications.js
@@ -1,3 +1,20 @@
+/*
+ * 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 './Notifications.scss';
import dispatcher from "../dispatcher";
diff --git a/frontend/src/components/fields/ButtonField.js b/frontend/src/components/fields/ButtonField.js
index cc32b0f..ffcceae 100644
--- a/frontend/src/components/fields/ButtonField.js
+++ b/frontend/src/components/fields/ButtonField.js
@@ -1,3 +1,20 @@
+/*
+ * 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 './ButtonField.scss';
import './common.scss';
diff --git a/frontend/src/components/fields/CheckField.js b/frontend/src/components/fields/CheckField.js
index 33f4f83..dd44970 100644
--- a/frontend/src/components/fields/CheckField.js
+++ b/frontend/src/components/fields/CheckField.js
@@ -1,3 +1,20 @@
+/*
+ * 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 './CheckField.scss';
import './common.scss';
diff --git a/frontend/src/components/fields/ChoiceField.js b/frontend/src/components/fields/ChoiceField.js
index 73e950d..14071c3 100644
--- a/frontend/src/components/fields/ChoiceField.js
+++ b/frontend/src/components/fields/ChoiceField.js
@@ -1,3 +1,20 @@
+/*
+ * 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 './ChoiceField.scss';
import './common.scss';
diff --git a/frontend/src/components/fields/InputField.js b/frontend/src/components/fields/InputField.js
index 84c981b..80cce3b 100644
--- a/frontend/src/components/fields/InputField.js
+++ b/frontend/src/components/fields/InputField.js
@@ -1,3 +1,20 @@
+/*
+ * 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 './InputField.scss';
import './common.scss';
diff --git a/frontend/src/components/fields/TextField.js b/frontend/src/components/fields/TextField.js
index de68c21..9237c0c 100644
--- a/frontend/src/components/fields/TextField.js
+++ b/frontend/src/components/fields/TextField.js
@@ -1,3 +1,20 @@
+/*
+ * 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.scss';
import './common.scss';
diff --git a/frontend/src/components/fields/extensions/ColorField.js b/frontend/src/components/fields/extensions/ColorField.js
index 96ebc49..f1c0caf 100644
--- a/frontend/src/components/fields/extensions/ColorField.js
+++ b/frontend/src/components/fields/extensions/ColorField.js
@@ -1,3 +1,20 @@
+/*
+ * 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 {OverlayTrigger, Popover} from "react-bootstrap";
import './ColorField.scss';
diff --git a/frontend/src/components/fields/extensions/NumericField.js b/frontend/src/components/fields/extensions/NumericField.js
index 19a9e46..d4d027d 100644
--- a/frontend/src/components/fields/extensions/NumericField.js
+++ b/frontend/src/components/fields/extensions/NumericField.js
@@ -1,3 +1,20 @@
+/*
+ * 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 InputField from "../InputField";
diff --git a/frontend/src/components/filters/BooleanConnectionsFilter.js b/frontend/src/components/filters/BooleanConnectionsFilter.js
index 4c5a78a..a9a420e 100644
--- a/frontend/src/components/filters/BooleanConnectionsFilter.js
+++ b/frontend/src/components/filters/BooleanConnectionsFilter.js
@@ -1,3 +1,20 @@
+/*
+ * 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";
diff --git a/frontend/src/components/filters/FiltersDefinitions.js b/frontend/src/components/filters/FiltersDefinitions.js
index d4f2912..cde3cfb 100644
--- a/frontend/src/components/filters/FiltersDefinitions.js
+++ b/frontend/src/components/filters/FiltersDefinitions.js
@@ -1,3 +1,20 @@
+/*
+ * 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 {cleanNumber, validateIpAddress, validateMin, validatePort} from "../../utils";
import StringConnectionsFilter from "./StringConnectionsFilter";
import React from "react";
diff --git a/frontend/src/components/filters/RulesConnectionsFilter.js b/frontend/src/components/filters/RulesConnectionsFilter.js
index 8366189..48affb0 100644
--- a/frontend/src/components/filters/RulesConnectionsFilter.js
+++ b/frontend/src/components/filters/RulesConnectionsFilter.js
@@ -1,3 +1,20 @@
+/*
+ * 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";
diff --git a/frontend/src/components/filters/StringConnectionsFilter.js b/frontend/src/components/filters/StringConnectionsFilter.js
index f463593..a3b45dc 100644
--- a/frontend/src/components/filters/StringConnectionsFilter.js
+++ b/frontend/src/components/filters/StringConnectionsFilter.js
@@ -1,3 +1,20 @@
+/*
+ * 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";
diff --git a/frontend/src/components/objects/LinkPopover.js b/frontend/src/components/objects/LinkPopover.js
index 8768caa..3c5bf67 100644
--- a/frontend/src/components/objects/LinkPopover.js
+++ b/frontend/src/components/objects/LinkPopover.js
@@ -1,3 +1,20 @@
+/*
+ * 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 {randomClassName} from "../../utils";
import {OverlayTrigger, Popover} from "react-bootstrap";
diff --git a/frontend/src/components/panels/ConfigurationPane.js b/frontend/src/components/panels/ConfigurationPane.js
index 10309f6..9ae2cfb 100644
--- a/frontend/src/components/panels/ConfigurationPane.js
+++ b/frontend/src/components/panels/ConfigurationPane.js
@@ -1,3 +1,20 @@
+/*
+ * 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 './common.scss';
import './ConfigurationPane.scss';
diff --git a/frontend/src/components/panels/MainPane.js b/frontend/src/components/panels/MainPane.js
index bd25e0c..d34d58a 100644
--- a/frontend/src/components/panels/MainPane.js
+++ b/frontend/src/components/panels/MainPane.js
@@ -1,3 +1,20 @@
+/*
+ * 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 './common.scss';
import './MainPane.scss';
diff --git a/frontend/src/components/panels/PcapPane.js b/frontend/src/components/panels/PcapPane.js
index 13f7cb3..d5c2225 100644
--- a/frontend/src/components/panels/PcapPane.js
+++ b/frontend/src/components/panels/PcapPane.js
@@ -1,3 +1,20 @@
+/*
+ * 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 './PcapPane.scss';
import './common.scss';
diff --git a/frontend/src/components/panels/RulePane.js b/frontend/src/components/panels/RulePane.js
index 76f3ac0..9913962 100644
--- a/frontend/src/components/panels/RulePane.js
+++ b/frontend/src/components/panels/RulePane.js
@@ -1,3 +1,20 @@
+/*
+ * 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 './common.scss';
import './RulePane.scss';
diff --git a/frontend/src/components/panels/ServicePane.js b/frontend/src/components/panels/ServicePane.js
index 22c6655..fc7004b 100644
--- a/frontend/src/components/panels/ServicePane.js
+++ b/frontend/src/components/panels/ServicePane.js
@@ -1,3 +1,20 @@
+/*
+ * 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 './common.scss';
import './ServicePane.scss';
diff --git a/frontend/src/dispatcher.js b/frontend/src/dispatcher.js
index 4b8b5a4..943f7ec 100644
--- a/frontend/src/dispatcher.js
+++ b/frontend/src/dispatcher.js
@@ -1,3 +1,19 @@
+/*
+ * 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 .
+ */
class Dispatcher {
diff --git a/frontend/src/index.js b/frontend/src/index.js
index beb52ae..e3e48de 100644
--- a/frontend/src/index.js
+++ b/frontend/src/index.js
@@ -1,3 +1,20 @@
+/*
+ * 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 from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';
diff --git a/frontend/src/log.js b/frontend/src/log.js
index 0883962..424e1b4 100644
--- a/frontend/src/log.js
+++ b/frontend/src/log.js
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
const log = {
debug: (...obj) => console.info(...obj),
info: (...obj) => console.info(...obj),
diff --git a/frontend/src/notifications.js b/frontend/src/notifications.js
index 2a77ffb..f04036d 100644
--- a/frontend/src/notifications.js
+++ b/frontend/src/notifications.js
@@ -1,3 +1,20 @@
+/*
+ * 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 log from "./log";
import dispatcher from "./dispatcher";
diff --git a/frontend/src/setupProxy.js b/frontend/src/setupProxy.js
index 6f082c8..f2e1c39 100644
--- a/frontend/src/setupProxy.js
+++ b/frontend/src/setupProxy.js
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
diff --git a/frontend/src/utils.js b/frontend/src/utils.js
index aacc625..f333f0d 100644
--- a/frontend/src/utils.js
+++ b/frontend/src/utils.js
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
const timeRegex = /^(0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/;
export function createCurlCommand(subCommand, method = null, json = null, data = null) {
diff --git a/frontend/src/validation.js b/frontend/src/validation.js
index 7089d7f..87b08de 100644
--- a/frontend/src/validation.js
+++ b/frontend/src/validation.js
@@ -1,3 +1,19 @@
+/*
+ * 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 .
+ */
const validation = {
isValidColor: (color) => /^#(?:[0-9a-fA-F]{3}){1,2}$/.test(color),
diff --git a/frontend/src/views/App.js b/frontend/src/views/App.js
index 4bb9f57..8105117 100644
--- a/frontend/src/views/App.js
+++ b/frontend/src/views/App.js
@@ -1,3 +1,20 @@
+/*
+ * 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 './App.scss';
import Header from "./Header";
diff --git a/frontend/src/views/Connections.js b/frontend/src/views/Connections.js
index e835dcb..b2edd3f 100644
--- a/frontend/src/views/Connections.js
+++ b/frontend/src/views/Connections.js
@@ -1,3 +1,20 @@
+/*
+ * 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 './Connections.scss';
import Connection from "../components/Connection";
diff --git a/frontend/src/views/Filters.js b/frontend/src/views/Filters.js
index ba7d467..3dd8280 100644
--- a/frontend/src/views/Filters.js
+++ b/frontend/src/views/Filters.js
@@ -1,3 +1,20 @@
+/*
+ * 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 {Col, Container, Modal, Row, Table} from "react-bootstrap";
import {filtersDefinitions, filtersNames} from "../components/filters/FiltersDefinitions";
@@ -31,7 +48,7 @@ class Filters extends Component {
this.checkboxChangesHandler(name, event)} /> |
+ onChange={event => this.checkboxChangesHandler(name, event)}/>
{filtersDefinitions[name]} |
);
@@ -89,7 +106,7 @@ class Filters extends Component {
-
+
);
diff --git a/frontend/src/views/Header.js b/frontend/src/views/Header.js
index f5eff17..2cfe9fb 100644
--- a/frontend/src/views/Header.js
+++ b/frontend/src/views/Header.js
@@ -1,3 +1,20 @@
+/*
+ * 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 Typed from 'typed.js';
import './Header.scss';
diff --git a/frontend/src/views/Timeline.js b/frontend/src/views/Timeline.js
index 3adbf88..ebe3eb9 100644
--- a/frontend/src/views/Timeline.js
+++ b/frontend/src/views/Timeline.js
@@ -1,3 +1,20 @@
+/*
+ * 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 './Timeline.scss';
import {
diff --git a/notification_controller.go b/notification_controller.go
index 88c9e8c..3fa3c5b 100644
--- a/notification_controller.go
+++ b/notification_controller.go
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package main
import (
diff --git a/parsers/http_request_parser.go b/parsers/http_request_parser.go
index e2224b8..bc98f8f 100644
--- a/parsers/http_request_parser.go
+++ b/parsers/http_request_parser.go
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package parsers
import (
diff --git a/parsers/http_response_parser.go b/parsers/http_response_parser.go
index 1770116..e5ef1ac 100644
--- a/parsers/http_response_parser.go
+++ b/parsers/http_response_parser.go
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package parsers
import (
diff --git a/parsers/parser.go b/parsers/parser.go
index 06cc0dc..a29b1ab 100644
--- a/parsers/parser.go
+++ b/parsers/parser.go
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package parsers
type Parser interface {
diff --git a/parsers/parser_utils.go b/parsers/parser_utils.go
index b688262..575b666 100644
--- a/parsers/parser_utils.go
+++ b/parsers/parser_utils.go
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package parsers
import (
diff --git a/pcap_importer.go b/pcap_importer.go
index 78a5e6c..41ed082 100644
--- a/pcap_importer.go
+++ b/pcap_importer.go
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package main
import (
diff --git a/pcap_importer_test.go b/pcap_importer_test.go
index be09ea9..8940060 100644
--- a/pcap_importer_test.go
+++ b/pcap_importer_test.go
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package main
import (
diff --git a/resources_controller.go b/resources_controller.go
index 050157a..10b31e3 100644
--- a/resources_controller.go
+++ b/resources_controller.go
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package main
import (
diff --git a/rules_manager.go b/rules_manager.go
index a5dc7ce..636fc74 100644
--- a/rules_manager.go
+++ b/rules_manager.go
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package main
import (
diff --git a/rules_manager_test.go b/rules_manager_test.go
index a2ec501..dded096 100644
--- a/rules_manager_test.go
+++ b/rules_manager_test.go
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package main
import (
diff --git a/services_controller.go b/services_controller.go
index 9907b5e..e5fa200 100644
--- a/services_controller.go
+++ b/services_controller.go
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package main
import (
diff --git a/statistics_controller.go b/statistics_controller.go
index 65c7d58..006b230 100644
--- a/statistics_controller.go
+++ b/statistics_controller.go
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package main
import (
diff --git a/storage.go b/storage.go
index 0888ce0..f8b7f9c 100644
--- a/storage.go
+++ b/storage.go
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package main
import (
diff --git a/storage_test.go b/storage_test.go
index 4caa30d..dd91e97 100644
--- a/storage_test.go
+++ b/storage_test.go
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package main
import (
diff --git a/stream_handler.go b/stream_handler.go
index bccdeee..48dba34 100644
--- a/stream_handler.go
+++ b/stream_handler.go
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package main
import (
diff --git a/stream_handler_test.go b/stream_handler_test.go
index 199ae5b..127aa82 100644
--- a/stream_handler_test.go
+++ b/stream_handler_test.go
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package main
import (
diff --git a/utils.go b/utils.go
index ec5a807..639fd94 100644
--- a/utils.go
+++ b/utils.go
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package main
import (
--
cgit v1.2.3-70-g09d2
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
---
connection_streams_controller.go | 44 +--
frontend/src/components/App.js | 62 +++
frontend/src/components/Connection.js | 145 -------
frontend/src/components/Connection.scss | 68 ----
frontend/src/components/ConnectionContent.js | 243 ------------
frontend/src/components/ConnectionContent.scss | 113 ------
frontend/src/components/ConnectionMatchedRules.js | 46 ---
.../src/components/ConnectionMatchedRules.scss | 23 --
frontend/src/components/Header.js | 111 ++++++
frontend/src/components/Header.scss | 34 ++
frontend/src/components/MessageAction.js | 68 ----
frontend/src/components/MessageAction.scss | 8 -
frontend/src/components/Timeline.js | 232 +++++++++++
frontend/src/components/Timeline.scss | 22 ++
frontend/src/components/dialogs/Filters.js | 116 ++++++
frontend/src/components/objects/Connection.js | 145 +++++++
frontend/src/components/objects/Connection.scss | 68 ++++
.../components/objects/ConnectionMatchedRules.js | 46 +++
.../components/objects/ConnectionMatchedRules.scss | 23 ++
frontend/src/components/objects/MessageAction.js | 68 ++++
frontend/src/components/objects/MessageAction.scss | 8 +
frontend/src/components/pages/ConfigurationPage.js | 179 +++++++++
.../src/components/pages/ConfigurationPage.scss | 18 +
frontend/src/components/pages/MainPage.js | 76 ++++
frontend/src/components/pages/MainPage.scss | 24 ++
.../src/components/pages/ServiceUnavailablePage.js | 34 ++
frontend/src/components/pages/common.scss | 16 +
.../src/components/panels/ConfigurationPane.js | 179 ---------
.../src/components/panels/ConfigurationPane.scss | 18 -
frontend/src/components/panels/ConnectionsPane.js | 304 ++++++++++++++
.../src/components/panels/ConnectionsPane.scss | 38 ++
frontend/src/components/panels/MainPane.js | 47 +--
frontend/src/components/panels/MainPane.scss | 17 -
frontend/src/components/panels/PcapPane.js | 273 -------------
frontend/src/components/panels/PcapPane.scss | 38 --
frontend/src/components/panels/PcapsPane.js | 273 +++++++++++++
frontend/src/components/panels/PcapsPane.scss | 38 ++
frontend/src/components/panels/RulePane.js | 438 ---------------------
frontend/src/components/panels/RulePane.scss | 32 --
frontend/src/components/panels/RulesPane.js | 438 +++++++++++++++++++++
frontend/src/components/panels/RulesPane.scss | 32 ++
frontend/src/components/panels/ServicePane.js | 212 ----------
frontend/src/components/panels/ServicePane.scss | 22 --
frontend/src/components/panels/ServicesPane.js | 212 ++++++++++
frontend/src/components/panels/ServicesPane.scss | 22 ++
frontend/src/components/panels/StreamsPane.js | 242 ++++++++++++
frontend/src/components/panels/StreamsPane.scss | 113 ++++++
frontend/src/index.js | 2 +-
frontend/src/views/App.js | 81 ----
frontend/src/views/App.scss | 16 -
frontend/src/views/Connections.js | 293 --------------
frontend/src/views/Connections.scss | 38 --
frontend/src/views/Filters.js | 116 ------
frontend/src/views/Header.js | 109 -----
frontend/src/views/Header.scss | 34 --
frontend/src/views/Timeline.js | 232 -----------
frontend/src/views/Timeline.scss | 22 --
57 files changed, 3015 insertions(+), 2956 deletions(-)
create mode 100644 frontend/src/components/App.js
delete mode 100644 frontend/src/components/Connection.js
delete mode 100644 frontend/src/components/Connection.scss
delete mode 100644 frontend/src/components/ConnectionContent.js
delete mode 100644 frontend/src/components/ConnectionContent.scss
delete mode 100644 frontend/src/components/ConnectionMatchedRules.js
delete mode 100644 frontend/src/components/ConnectionMatchedRules.scss
create mode 100644 frontend/src/components/Header.js
create mode 100644 frontend/src/components/Header.scss
delete mode 100644 frontend/src/components/MessageAction.js
delete mode 100644 frontend/src/components/MessageAction.scss
create mode 100644 frontend/src/components/Timeline.js
create mode 100644 frontend/src/components/Timeline.scss
create mode 100644 frontend/src/components/dialogs/Filters.js
create mode 100644 frontend/src/components/objects/Connection.js
create mode 100644 frontend/src/components/objects/Connection.scss
create mode 100644 frontend/src/components/objects/ConnectionMatchedRules.js
create mode 100644 frontend/src/components/objects/ConnectionMatchedRules.scss
create mode 100644 frontend/src/components/objects/MessageAction.js
create mode 100644 frontend/src/components/objects/MessageAction.scss
create mode 100644 frontend/src/components/pages/ConfigurationPage.js
create mode 100644 frontend/src/components/pages/ConfigurationPage.scss
create mode 100644 frontend/src/components/pages/MainPage.js
create mode 100644 frontend/src/components/pages/MainPage.scss
create mode 100644 frontend/src/components/pages/ServiceUnavailablePage.js
create mode 100644 frontend/src/components/pages/common.scss
delete mode 100644 frontend/src/components/panels/ConfigurationPane.js
delete mode 100644 frontend/src/components/panels/ConfigurationPane.scss
create mode 100644 frontend/src/components/panels/ConnectionsPane.js
create mode 100644 frontend/src/components/panels/ConnectionsPane.scss
delete mode 100644 frontend/src/components/panels/PcapPane.js
delete mode 100644 frontend/src/components/panels/PcapPane.scss
create mode 100644 frontend/src/components/panels/PcapsPane.js
create mode 100644 frontend/src/components/panels/PcapsPane.scss
delete mode 100644 frontend/src/components/panels/RulePane.js
delete mode 100644 frontend/src/components/panels/RulePane.scss
create mode 100644 frontend/src/components/panels/RulesPane.js
create mode 100644 frontend/src/components/panels/RulesPane.scss
delete mode 100644 frontend/src/components/panels/ServicePane.js
delete mode 100644 frontend/src/components/panels/ServicePane.scss
create mode 100644 frontend/src/components/panels/ServicesPane.js
create mode 100644 frontend/src/components/panels/ServicesPane.scss
create mode 100644 frontend/src/components/panels/StreamsPane.js
create mode 100644 frontend/src/components/panels/StreamsPane.scss
delete mode 100644 frontend/src/views/App.js
delete mode 100644 frontend/src/views/App.scss
delete mode 100644 frontend/src/views/Connections.js
delete mode 100644 frontend/src/views/Connections.scss
delete mode 100644 frontend/src/views/Filters.js
delete mode 100644 frontend/src/views/Header.js
delete mode 100644 frontend/src/views/Header.scss
delete mode 100644 frontend/src/views/Timeline.js
delete mode 100644 frontend/src/views/Timeline.scss
(limited to 'frontend/src/views')
diff --git a/connection_streams_controller.go b/connection_streams_controller.go
index 9251a3a..89e484d 100644
--- a/connection_streams_controller.go
+++ b/connection_streams_controller.go
@@ -28,8 +28,7 @@ import (
)
const (
- initialPayloadsSize = 1024
- defaultQueryFormatLimit = 8024
+ initialMessagesSize = 1024
initialRegexSlicesCount = 8
pwntoolsMaxServerBytes = 20
)
@@ -66,8 +65,6 @@ type RegexSlice struct {
type GetMessageFormat struct {
Format string `form:"format"`
- Skip uint64 `form:"skip"`
- Limit uint64 `form:"limit"`
}
type DownloadMessageFormat struct {
@@ -92,12 +89,8 @@ func (csc ConnectionStreamsController) GetConnectionMessages(c context.Context,
return nil, false
}
- payloads := make([]*Message, 0, initialPayloadsSize)
- var clientIndex, serverIndex, globalIndex uint64
-
- if format.Limit <= 0 {
- format.Limit = defaultQueryFormatLimit
- }
+ messages := make([]*Message, 0, initialMessagesSize)
+ var clientIndex, serverIndex uint64
var clientBlocksIndex, serverBlocksIndex int
var clientDocumentIndex, serverDocumentIndex int
@@ -111,8 +104,8 @@ func (csc ConnectionStreamsController) GetConnectionMessages(c context.Context,
return serverBlocksIndex < len(serverStream.BlocksIndexes)
}
- var payload *Message
- payloadsBuffer := make([]*Message, 0, 16)
+ var message *Message
+ messagesBuffer := make([]*Message, 0, 16)
contentChunkBuffer := new(bytes.Buffer)
var lastContentSlice []byte
var sideChanged, lastClient, lastServer bool
@@ -129,7 +122,7 @@ func (csc ConnectionStreamsController) GetConnectionMessages(c context.Context,
}
size := uint64(end - start)
- payload = &Message{
+ message = &Message{
FromClient: true,
Content: DecodeBytes(clientStream.Payload[start:end], format.Format),
Index: start,
@@ -138,7 +131,6 @@ func (csc ConnectionStreamsController) GetConnectionMessages(c context.Context,
RegexMatches: findMatchesBetween(clientStream.PatternMatches, clientIndex, clientIndex+size),
}
clientIndex += size
- globalIndex += size
clientBlocksIndex++
lastContentSlice = clientStream.Payload[start:end]
@@ -153,7 +145,7 @@ func (csc ConnectionStreamsController) GetConnectionMessages(c context.Context,
}
size := uint64(end - start)
- payload = &Message{
+ message = &Message{
FromClient: false,
Content: DecodeBytes(serverStream.Payload[start:end], format.Format),
Index: start,
@@ -162,7 +154,6 @@ func (csc ConnectionStreamsController) GetConnectionMessages(c context.Context,
RegexMatches: findMatchesBetween(serverStream.PatternMatches, serverIndex, serverIndex+size),
}
serverIndex += size
- globalIndex += size
serverBlocksIndex++
lastContentSlice = serverStream.Payload[start:end]
@@ -172,49 +163,43 @@ func (csc ConnectionStreamsController) GetConnectionMessages(c context.Context,
if !hasClientBlocks() {
clientDocumentIndex++
clientBlocksIndex = 0
+ clientIndex = 0
clientStream = csc.getConnectionStream(c, connectionID, true, clientDocumentIndex)
}
if !hasServerBlocks() {
serverDocumentIndex++
serverBlocksIndex = 0
+ serverIndex = 0
serverStream = csc.getConnectionStream(c, connectionID, false, serverDocumentIndex)
}
updateMetadata := func() {
metadata := parsers.Parse(contentChunkBuffer.Bytes())
var isMetadataContinuation bool
- for _, elem := range payloadsBuffer {
+ for _, elem := range messagesBuffer {
elem.Metadata = metadata
elem.IsMetadataContinuation = isMetadataContinuation
isMetadataContinuation = true
}
- payloadsBuffer = payloadsBuffer[:0]
+ messagesBuffer = messagesBuffer[:0]
contentChunkBuffer.Reset()
}
if sideChanged {
updateMetadata()
}
- payloadsBuffer = append(payloadsBuffer, payload)
+ messagesBuffer = append(messagesBuffer, message)
contentChunkBuffer.Write(lastContentSlice)
if clientStream.ID.IsZero() && serverStream.ID.IsZero() {
updateMetadata()
}
- if globalIndex > format.Skip {
- // problem: waste of time if the payload is discarded
- payloads = append(payloads, payload)
- }
- if globalIndex > format.Skip+format.Limit {
- // problem: the last chunk is not parsed, but can be ok because it is not finished
- updateMetadata()
- return payloads, true
- }
+ messages = append(messages, message)
}
- return payloads, true
+ return messages, true
}
func (csc ConnectionStreamsController) DownloadConnectionMessages(c context.Context, connectionID RowID,
@@ -345,7 +330,6 @@ func findMatchesBetween(patternMatches map[uint][]PatternSlice, from, to uint64)
continue
}
- log.Info(slice[0], slice[1], from, to)
var start, end uint64
if from > slice[0] {
start = 0
diff --git a/frontend/src/components/App.js b/frontend/src/components/App.js
new file mode 100644
index 0000000..bf959c5
--- /dev/null
+++ b/frontend/src/components/App.js
@@ -0,0 +1,62 @@
+/*
+ * 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 ConfigurationPage from "./pages/ConfigurationPage";
+import Notifications from "./Notifications";
+import dispatcher from "../dispatcher";
+import MainPage from "./pages/MainPage";
+import ServiceUnavailablePage from "./pages/ServiceUnavailablePage";
+
+class App extends Component {
+
+ state = {};
+
+ componentDidMount() {
+ dispatcher.register("notifications", payload => {
+ if (payload.event === "connected") {
+ this.setState({
+ connected: true,
+ configured: payload.message["is_configured"]
+ });
+ }
+ });
+
+ setInterval(() => {
+ if (document.title.endsWith("❚")) {
+ document.title = document.title.slice(0, -1);
+ } else {
+ document.title += "❚";
+ }
+ }, 500);
+ }
+
+ render() {
+ return (
+ <>
+
+ {this.state.connected ?
+ (this.state.configured ? :
+ this.setState({configured: true})}/>) :
+
+ }
+ >
+ );
+ }
+}
+
+export default App;
diff --git a/frontend/src/components/Connection.js b/frontend/src/components/Connection.js
deleted file mode 100644
index c7b0010..0000000
--- a/frontend/src/components/Connection.js
+++ /dev/null
@@ -1,145 +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 './Connection.scss';
-import {Form, OverlayTrigger, Popover} from "react-bootstrap";
-import backend from "../backend";
-import {dateTimeToTime, durationBetween, formatSize} from "../utils";
-import ButtonField from "./fields/ButtonField";
-import LinkPopover from "./objects/LinkPopover";
-
-const classNames = require('classnames');
-
-class Connection extends Component {
-
- constructor(props) {
- super(props);
- this.state = {
- update: false,
- copiedMessage: false
- };
-
- this.copyTextarea = React.createRef();
- this.handleAction = this.handleAction.bind(this);
- }
-
- handleAction(name) {
- if (name === "hide") {
- const enabled = !this.props.data.hidden;
- backend.post(`/api/connections/${this.props.data.id}/${enabled ? "hide" : "show"}`)
- .then(_ => {
- this.props.onEnabled(!enabled);
- this.setState({update: true});
- });
- }
- if (name === "mark") {
- const marked = this.props.data.marked;
- backend.post(`/api/connections/${this.props.data.id}/${marked ? "unmark" : "mark"}`)
- .then(_ => {
- this.props.onMarked(!marked);
- 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;
- let serviceName = "/dev/null";
- let serviceColor = "#0f192e";
- if (this.props.services[conn["port_dst"]]) {
- const service = this.props.services[conn["port_dst"]];
- serviceName = service.name;
- serviceColor = service.color;
- }
- let startedAt = new Date(conn["started_at"]);
- let closedAt = new Date(conn["closed_at"]);
- let processedAt = new Date(conn["processed_at"]);
- let timeInfo =
- Started at {startedAt.toLocaleDateString() + " " + startedAt.toLocaleTimeString()}
- Processed at {processedAt.toLocaleDateString() + " " + processedAt.toLocaleTimeString()}
- Closed at {closedAt.toLocaleDateString() + " " + closedAt.toLocaleTimeString()}
- ;
-
- const popoverFor = function (name, content) {
- return
-
- {content}
-
- ;
- };
-
- const commentPopoverContent =
- Click to {conn.comment.length > 0 ? "edit" : "add"} comment
- {conn.comment && }
- ;
-
- const copyPopoverContent =
- {this.state.copiedMessage ? Copied! :
- Click to copy the connection id}
-
- ;
-
- return (
- 0})}>
-
-
- this.props.addServicePortFilter(conn["port_dst"])}/>
-
- |
- {conn["ip_src"]} |
- {conn["port_src"]} |
- {conn["ip_dst"]} |
- {conn["port_dst"]} |
-
-
- |
- {durationBetween(startedAt, closedAt)} |
- {formatSize(conn["client_bytes"])} |
- {formatSize(conn["server_bytes"])} |
-
- Mark this connection)}>
- this.handleAction("mark")}>!!
-
-
- this.handleAction("comment")}>@
-
-
- this.handleAction("copy")}>#
-
- |
-
- );
- }
-
-}
-
-export default Connection;
diff --git a/frontend/src/components/Connection.scss b/frontend/src/components/Connection.scss
deleted file mode 100644
index cc1ea96..0000000
--- a/frontend/src/components/Connection.scss
+++ /dev/null
@@ -1,68 +0,0 @@
-@import "../colors.scss";
-
-.connection {
- border-top: 3px solid $color-primary-3;
- border-bottom: 3px solid $color-primary-3;
- background-color: $color-primary-0;
-
- td {
- vertical-align: middle;
- }
-
- .connection-service {
- .btn {
- width: 100%;
- }
-
- .btn:hover {
- color: $color-primary-4;
- background-color: $color-primary-1 !important;
- }
- }
-
- .connection-icon {
- font-size: 18px;
- font-weight: 600;
- margin-right: 6px;
- cursor: pointer;
- }
-
- .connection-icon.icon-enabled {
- color: $color-secondary-2;
- }
-
- &:hover {
- background-color: $color-primary-2;
- }
-
- &.connection-selected {
- background-color: $color-primary-2;
- }
-
- &.has-matched-rules {
- border-bottom: 0;
- }
-
- .link-popover {
- font-weight: 400;
- }
-}
-
-.connection-popover {
- border: none;
- background-color: $color-primary-4;
-
- .popover-body {
- color: $color-primary-1;
-
- textarea {
- font-size: 12px;
- width: 200px;
- background-color: $color-primary-3;
- }
- }
-
- .arrow::after {
- border-right-color: $color-primary-4;
- }
-}
diff --git a/frontend/src/components/ConnectionContent.js b/frontend/src/components/ConnectionContent.js
deleted file mode 100644
index b468277..0000000
--- a/frontend/src/components/ConnectionContent.js
+++ /dev/null
@@ -1,243 +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 './ConnectionContent.scss';
-import {Row} from 'react-bootstrap';
-import MessageAction from "./MessageAction";
-import backend from "../backend";
-import ButtonField from "./fields/ButtonField";
-import ChoiceField from "./fields/ChoiceField";
-import DOMPurify from 'dompurify';
-import ReactJson from 'react-json-view'
-import {downloadBlob, getHeaderValue} from "../utils";
-import log from "../log";
-
-const classNames = require('classnames');
-
-class ConnectionContent extends Component {
-
- constructor(props) {
- super(props);
- this.state = {
- loading: false,
- connectionContent: null,
- format: "default",
- tryParse: true,
- messageActionDialog: null
- };
-
- this.validFormats = ["default", "hex", "hexdump", "base32", "base64", "ascii", "binary", "decimal", "octal"];
- }
-
- componentDidMount() {
- if (this.props.connection != null) {
- this.loadStream();
- }
-
- document.title = "caronte:~/$";
- }
-
- componentDidUpdate(prevProps, prevState, snapshot) {
- if (this.props.connection != null && (
- this.props.connection !== prevProps.connection || this.state.format !== prevState.format)) {
- this.closeRenderWindow();
- this.loadStream();
- }
- }
-
- componentWillUnmount() {
- this.closeRenderWindow();
- }
-
- loadStream = () => {
- this.setState({loading: true});
- // TODO: limit workaround.
- backend.get(`/api/streams/${this.props.connection.id}?format=${this.state.format}&limit=999999`).then(res => {
- this.setState({
- connectionContent: res.json,
- loading: false
- });
- });
- };
-
- setFormat = (format) => {
- if (this.validFormats.includes(format)) {
- this.setState({format: format});
- }
- };
-
- tryParseConnectionMessage = (connectionMessage) => {
- if (connectionMessage.metadata == null) {
- return connectionMessage.content;
- }
- if (connectionMessage["is_metadata_continuation"]) {
- return **already parsed in previous messages**;
- }
-
- let unrollMap = (obj) => obj == null ? null : Object.entries(obj).map(([key, value]) =>
- {key}: {value}
- );
-
- let m = connectionMessage.metadata;
- switch (m.type) {
- case "http-request":
- let url = {m.host}{m.url};
- return
- {m.method} {url} {m.protocol}
- {unrollMap(m.headers)}
- {m.body}
- {unrollMap(m.trailers)}
- ;
- case "http-response":
- const contentType = getHeaderValue(m, "Content-Type");
- let body = m.body;
- if (contentType && contentType.includes("application/json")) {
- try {
- const json = JSON.parse(m.body);
- body = ;
- } catch (e) {
- console.log(e);
- }
- }
-
- return
- {m.protocol} {m.status}
- {unrollMap(m.headers)}
- {body}
- {unrollMap(m.trailers)}
- ;
- default:
- return connectionMessage.content;
- }
- };
-
- connectionsActions = (connectionMessage) => {
- if (connectionMessage.metadata == null) { //} || !connectionMessage.metadata["reproducers"]) {
- return null;
- }
-
- const m = connectionMessage.metadata;
- switch (m.type) {
- case "http-request" :
- if (!connectionMessage.metadata["reproducers"]) {
- return;
- }
- return Object.entries(connectionMessage.metadata["reproducers"]).map(([actionName, actionValue]) =>
- {
- this.setState({
- messageActionDialog: this.setState({messageActionDialog: null})}/>
- });
- }}/>
- );
- case "http-response":
- const contentType = getHeaderValue(m, "Content-Type");
-
- if (contentType && contentType.includes("text/html")) {
- return {
- let w;
- if (this.state.renderWindow && !this.state.renderWindow.closed) {
- w = this.state.renderWindow;
- } else {
- w = window.open("", "", "width=900, height=600, scrollbars=yes");
- this.setState({renderWindow: w});
- }
- w.document.body.innerHTML = DOMPurify.sanitize(m.body);
- w.focus();
- }}/>;
- }
- break;
- default:
- return null;
- }
- };
-
- downloadStreamRaw = (value) => {
- backend.download(`/api/streams/${this.props.connection.id}/download?format=${this.state.format}&type=${value}`)
- .then(res => downloadBlob(res.blob, `${this.props.connection.id}-${value}-${this.state.format}.txt`))
- .catch(_ => log.error("Failed to download stream messages"));
- };
-
- closeRenderWindow = () => {
- if (this.state.renderWindow) {
- this.state.renderWindow.close();
- }
- };
-
- render() {
- const conn = this.props.connection;
- const content = this.state.connectionContent;
-
- if (content == null) {
- return select a connection to view ;
- }
-
- let payload = content.map((c, i) =>
-
-
-
-
- offset: {c.index} | timestamp: {c.timestamp}
- | retransmitted: {c["is_retransmitted"] ? "yes" : "no"}
-
- {this.connectionsActions(c)}
-
-
- {c["from_client"] ? "client" : "server"}
-
- {this.state.tryParse && this.state.format === "default" ? this.tryParseConnectionMessage(c) : c.content}
-
-
- );
-
- return (
-
-
-
-
- flow: {conn["ip_src"]}:{conn["port_src"]} -> {conn["ip_dst"]}:{conn["port_dst"]}
- | timestamp: {conn["started_at"]}
-
-
-
-
-
-
-
-
-
-
-
- {payload}
- {this.state.messageActionDialog}
-
- );
- }
-
-}
-
-
-export default ConnectionContent;
diff --git a/frontend/src/components/ConnectionContent.scss b/frontend/src/components/ConnectionContent.scss
deleted file mode 100644
index f4edec9..0000000
--- a/frontend/src/components/ConnectionContent.scss
+++ /dev/null
@@ -1,113 +0,0 @@
-@import "../colors.scss";
-
-.connection-content {
- height: 100%;
- background-color: $color-primary-0;
-
- pre {
- overflow-x: hidden;
- height: calc(100% - 31px);
- padding: 0 10px;
- white-space: pre-wrap;
- word-break: break-word;
-
- p {
- margin: 0;
- padding: 0;
- }
- }
-
- .connection-message {
- position: relative;
- margin: 10px 0;
- border: 4px solid $color-primary-3;
- border-top: 0;
-
- .connection-message-header {
- height: 25px;
- background-color: $color-primary-3;
-
- .connection-message-info {
- font-size: 11px;
- margin-top: 6px;
- margin-left: -10px;
- }
-
- .connection-message-actions {
- display: none;
- margin-right: -18px;
-
- button {
- font-size: 11px;
- margin: 0 3px;
- padding: 5px;
- }
- }
- }
-
- .message-content {
- padding: 10px;
-
- .react-json-view {
- background-color: inherit !important;
- }
- }
-
- &:hover .connection-message-actions {
- display: flex;
- }
-
- .connection-message-label {
- font-size: 12px;
- position: absolute;
- top: 0;
- padding: 10px 0;
- background-color: $color-primary-3;
- writing-mode: vertical-rl;
- text-orientation: mixed;
- }
-
- &.from-client {
- margin-right: 100px;
- color: $color-primary-4;
-
- .connection-message-label {
- right: -22px;
- }
- }
-
- &.from-server {
- margin-left: 100px;
- color: $color-primary-4;
-
- .connection-message-label {
- left: -22px;
- transform: rotate(-180deg);
- }
- }
- }
-
- .connection-content-header {
- height: 33px;
- padding: 0;
- background-color: $color-primary-3;
-
- .header-info {
- font-size: 12px;
- padding-top: 7px;
- padding-left: 25px;
- }
-
- .header-actions {
- display: flex;
-
- .choice-field {
- margin-top: -5px;
-
- .field-value {
- background-color: $color-primary-3;
- }
- }
- }
- }
-}
diff --git a/frontend/src/components/ConnectionMatchedRules.js b/frontend/src/components/ConnectionMatchedRules.js
deleted file mode 100644
index 35643c5..0000000
--- a/frontend/src/components/ConnectionMatchedRules.js
+++ /dev/null
@@ -1,46 +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 './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;
diff --git a/frontend/src/components/ConnectionMatchedRules.scss b/frontend/src/components/ConnectionMatchedRules.scss
deleted file mode 100644
index 65d9ac8..0000000
--- a/frontend/src/components/ConnectionMatchedRules.scss
+++ /dev/null
@@ -1,23 +0,0 @@
-@import "../colors.scss";
-
-.connection-matches {
- background-color: $color-primary-0;
-
- .rule-buttons {
- padding: 0;
- }
-
- .button-field {
- display: inline-block;
- margin-right: 5px;
-
- button {
- font-size: 0.8em;
- padding: 2px 10px;
- }
- }
-
- .row-label {
- font-size: 0.8em;
- }
-}
diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js
new file mode 100644
index 0000000..4d29364
--- /dev/null
+++ b/frontend/src/components/Header.js
@@ -0,0 +1,111 @@
+/*
+ * 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 Typed from 'typed.js';
+import './Header.scss';
+import {filtersDefinitions, filtersNames} from "./filters/FiltersDefinitions";
+import {Link, withRouter} from "react-router-dom";
+import ButtonField from "./fields/ButtonField";
+
+class Header extends Component {
+
+ constructor(props) {
+ super(props);
+ let newState = {};
+ filtersNames.forEach(elem => newState[`${elem}_active`] = false);
+ this.state = newState;
+ this.fetchStateFromLocalStorage = this.fetchStateFromLocalStorage.bind(this);
+ }
+
+ componentDidMount() {
+ const options = {
+ strings: ["caronte$ "],
+ typeSpeed: 50,
+ cursorChar: "❚"
+ };
+ this.typed = new Typed(this.el, options);
+
+ this.fetchStateFromLocalStorage();
+
+ if (typeof window !== "undefined") {
+ window.addEventListener("quick-filters", this.fetchStateFromLocalStorage);
+ }
+ }
+
+ componentWillUnmount() {
+ this.typed.destroy();
+
+ if (typeof window !== "undefined") {
+ window.removeEventListener("quick-filters", this.fetchStateFromLocalStorage);
+ }
+ }
+
+ fetchStateFromLocalStorage() {
+ let newState = {};
+ filtersNames.forEach(elem => newState[`${elem}_active`] = localStorage.getItem(`filters.${elem}`) === "true");
+ this.setState(newState);
+ }
+
+ render() {
+ let quickFilters = filtersNames.filter(name => this.state[`${name}_active`])
+ .map(name => {filtersDefinitions[name]})
+ .slice(0, 5);
+
+ return (
+
+ );
+ }
+}
+
+export default withRouter(Header);
diff --git a/frontend/src/components/Header.scss b/frontend/src/components/Header.scss
new file mode 100644
index 0000000..e2e8e1c
--- /dev/null
+++ b/frontend/src/components/Header.scss
@@ -0,0 +1,34 @@
+@import "../colors";
+
+.header {
+ height: 80px;
+ padding: 15px 30px;
+
+ > .row {
+ background-color: $color-primary-0;
+ }
+
+ .header-title {
+ width: 200px;
+ margin: 5px 0 5px -5px;
+ }
+
+ .header-buttons {
+ display: flex;
+ justify-content: flex-end;
+ margin: 7px 0;
+
+ .button-field {
+ margin-left: 7px;
+ }
+ }
+
+ .filters-bar {
+ padding: 3px 0;
+
+ .filter {
+ display: inline-block;
+ margin-right: 10px;
+ }
+ }
+}
diff --git a/frontend/src/components/MessageAction.js b/frontend/src/components/MessageAction.js
deleted file mode 100644
index b94cbb9..0000000
--- a/frontend/src/components/MessageAction.js
+++ /dev/null
@@ -1,68 +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 './MessageAction.scss';
-import {Modal} from "react-bootstrap";
-import TextField from "./fields/TextField";
-import ButtonField from "./fields/ButtonField";
-
-class MessageAction extends Component {
-
- constructor(props) {
- super(props);
- this.state = {
- copyButtonText: "copy"
- };
- this.actionValue = React.createRef();
- this.copyActionValue = this.copyActionValue.bind(this);
- }
-
- copyActionValue() {
- this.actionValue.current.select();
- document.execCommand('copy');
- this.setState({copyButtonText: "copied!"});
- setTimeout(() => this.setState({copyButtonText: "copy"}), 3000);
- }
-
- render() {
- return (
-
-
-
- {this.props.actionName}
-
-
-
-
-
-
-
-
-
-
- );
- }
-}
-
-export default MessageAction;
diff --git a/frontend/src/components/MessageAction.scss b/frontend/src/components/MessageAction.scss
deleted file mode 100644
index faa23d3..0000000
--- a/frontend/src/components/MessageAction.scss
+++ /dev/null
@@ -1,8 +0,0 @@
-@import "../colors.scss";
-
-.message-action-value {
- font-size: 13px;
- padding: 15px;
- color: $color-primary-4;
- background-color: $color-primary-2;
-}
diff --git a/frontend/src/components/Timeline.js b/frontend/src/components/Timeline.js
new file mode 100644
index 0000000..7be42e0
--- /dev/null
+++ b/frontend/src/components/Timeline.js
@@ -0,0 +1,232 @@
+/*
+ * 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 './Timeline.scss';
+import {
+ ChartContainer,
+ ChartRow,
+ Charts,
+ LineChart,
+ MultiBrush,
+ Resizable,
+ 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";
+
+const minutes = 60 * 1000;
+
+class Timeline extends Component {
+
+ state = {
+ metric: "connections_per_service"
+ };
+
+ constructor() {
+ super();
+
+ this.disableTimeSeriesChanges = false;
+ this.selectionTimeout = null;
+ }
+
+ filteredPort = () => {
+ const urlParams = new URLSearchParams(this.props.location.search);
+ 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"));
+
+ dispatcher.register("connection_updates", payload => {
+ this.setState({
+ selection: new TimeRange(payload.from, payload.to),
+ });
+ });
+
+ dispatcher.register("notifications", payload => {
+ if (payload.event === "services.edit") {
+ this.loadServices().then(() => log.debug("Services reloaded after notification update"));
+ }
+ });
+ }
+
+ 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"));
+ }
+ }
+
+ loadStatistics = async (metric, filteredPort) => {
+ 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;
+ }
+
+ const ports = Object.keys(services);
+ ports.forEach(s => urlParams.append("ports", s));
+
+ const metrics = (await backend.get("/api/statistics?" + urlParams)).json;
+ const zeroFilledMetrics = [];
+ const toTime = m => new Date(m["range_start"]).getTime();
+
+ if (metrics.length > 0) {
+ let i = 0;
+ for (let interval = toTime(metrics[0]); interval <= toTime(metrics[metrics.length - 1]); interval += minutes) {
+ if (interval === toTime(metrics[i])) {
+ const m = metrics[i++];
+ m["range_start"] = new Date(m["range_start"]);
+ zeroFilledMetrics.push(m);
+ } else {
+ const m = {};
+ m["range_start"] = new Date(interval);
+ m[metric] = {};
+ ports.forEach(p => m[metric][p] = 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)))
+ });
+ 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),
+ start,
+ end
+ });
+ log.debug(`Loaded statistics for metric "${metric}" for services [${ports}]`);
+ };
+
+ loadServices = async () => {
+ const services = (await backend.get("/api/services")).json;
+ this.setState({services});
+ return services;
+ };
+
+ createStyler = () => {
+ return styler(Object.keys(this.state.services).map(port => {
+ return {key: port, color: this.state.services[port].color, width: 2};
+ }));
+ };
+
+ handleTimeRangeChange = (timeRange) => {
+ if (!this.disableTimeSeriesChanges) {
+ this.setState({timeRange});
+ }
+ };
+
+ handleSelectionChange = (timeRange) => {
+ this.disableTimeSeriesChanges = true;
+
+ this.setState({selection: timeRange});
+ if (this.selectionTimeout) {
+ clearTimeout(this.selectionTimeout);
+ }
+ this.selectionTimeout = setTimeout(() => {
+ dispatcher.dispatch("timeline_updates", {
+ from: timeRange.begin(),
+ to: timeRange.end()
+ });
+ this.selectionTimeout = null;
+ this.disableTimeSeriesChanges = false;
+ }, 1000);
+ };
+
+ aggregateSeries = (func) => {
+ const values = this.state.series.columns().map(c => this.state.series[func](c));
+ return Math[func](...values);
+ };
+
+ render() {
+ if (!this.state.series) {
+ return null;
+ }
+
+ return (
+
+ );
+ }
+}
+
+export default withRouter(Timeline);
diff --git a/frontend/src/components/Timeline.scss b/frontend/src/components/Timeline.scss
new file mode 100644
index 0000000..eeb9d50
--- /dev/null
+++ b/frontend/src/components/Timeline.scss
@@ -0,0 +1,22 @@
+@import "../colors";
+
+.footer {
+ padding: 15px;
+
+ .time-line {
+ position: relative;
+ background-color: $color-primary-0;
+
+ .metric-selection {
+ font-size: 0.8em;
+ position: absolute;
+ top: 5px;
+ right: 10px;
+ }
+ }
+
+ svg text {
+ font-family: "Fira Code", monospace !important;
+ fill: $color-primary-4 !important;
+ }
+}
diff --git a/frontend/src/components/dialogs/Filters.js b/frontend/src/components/dialogs/Filters.js
new file mode 100644
index 0000000..35c11df
--- /dev/null
+++ b/frontend/src/components/dialogs/Filters.js
@@ -0,0 +1,116 @@
+/*
+ * 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 {Col, Container, Modal, Row, Table} from "react-bootstrap";
+import {filtersDefinitions, filtersNames} from "../filters/FiltersDefinitions";
+import ButtonField from "../fields/ButtonField";
+
+class Filters extends Component {
+
+ constructor(props) {
+ super(props);
+ let newState = {};
+ filtersNames.forEach(elem => newState[`${elem}_active`] = false);
+ this.state = newState;
+ }
+
+ componentDidMount() {
+ let newState = {};
+ filtersNames.forEach(elem => newState[`${elem}_active`] = localStorage.getItem(`filters.${elem}`) === "true");
+ this.setState(newState);
+ }
+
+ checkboxChangesHandler(filterName, event) {
+ this.setState({[`${filterName}_active`]: event.target.checked});
+ localStorage.setItem(`filters.${filterName}`, event.target.checked);
+ if (typeof window !== "undefined") {
+ window.dispatchEvent(new Event("quick-filters"));
+ }
+ }
+
+ generateRows(filtersNames) {
+ return filtersNames.map(name =>
+
+ this.checkboxChangesHandler(name, event)}/> |
+ {filtersDefinitions[name]} |
+
+ );
+ }
+
+ render() {
+ return (
+
+
+
+ ~/filters
+
+
+
+
+
+
+
+
+
+ show |
+ filter |
+
+
+
+ {this.generateRows(["service_port", "client_address", "min_duration",
+ "min_bytes", "started_after", "closed_after", "marked"])}
+
+
+
+
+
+
+
+ show |
+ filter |
+
+
+
+ {this.generateRows(["matched_rules", "client_port", "max_duration",
+ "max_bytes", "started_before", "closed_before", "hidden"])}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default Filters;
diff --git a/frontend/src/components/objects/Connection.js b/frontend/src/components/objects/Connection.js
new file mode 100644
index 0000000..5e2beba
--- /dev/null
+++ b/frontend/src/components/objects/Connection.js
@@ -0,0 +1,145 @@
+/*
+ * 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 './Connection.scss';
+import {Form, OverlayTrigger, Popover} from "react-bootstrap";
+import backend from "../../backend";
+import {dateTimeToTime, durationBetween, formatSize} from "../../utils";
+import ButtonField from "../fields/ButtonField";
+import LinkPopover from "./LinkPopover";
+
+const classNames = require('classnames');
+
+class Connection extends Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ update: false,
+ copiedMessage: false
+ };
+
+ this.copyTextarea = React.createRef();
+ this.handleAction = this.handleAction.bind(this);
+ }
+
+ handleAction(name) {
+ if (name === "hide") {
+ const enabled = !this.props.data.hidden;
+ backend.post(`/api/connections/${this.props.data.id}/${enabled ? "hide" : "show"}`)
+ .then(_ => {
+ this.props.onEnabled(!enabled);
+ this.setState({update: true});
+ });
+ }
+ if (name === "mark") {
+ const marked = this.props.data.marked;
+ backend.post(`/api/connections/${this.props.data.id}/${marked ? "unmark" : "mark"}`)
+ .then(_ => {
+ this.props.onMarked(!marked);
+ 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;
+ let serviceName = "/dev/null";
+ let serviceColor = "#0f192e";
+ if (this.props.services[conn["port_dst"]]) {
+ const service = this.props.services[conn["port_dst"]];
+ serviceName = service.name;
+ serviceColor = service.color;
+ }
+ let startedAt = new Date(conn["started_at"]);
+ let closedAt = new Date(conn["closed_at"]);
+ let processedAt = new Date(conn["processed_at"]);
+ let timeInfo =
+ Started at {startedAt.toLocaleDateString() + " " + startedAt.toLocaleTimeString()}
+ Processed at {processedAt.toLocaleDateString() + " " + processedAt.toLocaleTimeString()}
+ Closed at {closedAt.toLocaleDateString() + " " + closedAt.toLocaleTimeString()}
+ ;
+
+ const popoverFor = function (name, content) {
+ return
+
+ {content}
+
+ ;
+ };
+
+ const commentPopoverContent =
+ Click to {conn.comment.length > 0 ? "edit" : "add"} comment
+ {conn.comment && }
+ ;
+
+ const copyPopoverContent =
+ {this.state.copiedMessage ? Copied! :
+ Click to copy the connection id}
+
+ ;
+
+ return (
+ 0})}>
+
+
+ this.props.addServicePortFilter(conn["port_dst"])}/>
+
+ |
+ {conn["ip_src"]} |
+ {conn["port_src"]} |
+ {conn["ip_dst"]} |
+ {conn["port_dst"]} |
+
+
+ |
+ {durationBetween(startedAt, closedAt)} |
+ {formatSize(conn["client_bytes"])} |
+ {formatSize(conn["server_bytes"])} |
+
+ Mark this connection)}>
+ this.handleAction("mark")}>!!
+
+
+ this.handleAction("comment")}>@
+
+
+ this.handleAction("copy")}>#
+
+ |
+
+ );
+ }
+
+}
+
+export default Connection;
diff --git a/frontend/src/components/objects/Connection.scss b/frontend/src/components/objects/Connection.scss
new file mode 100644
index 0000000..3b9f479
--- /dev/null
+++ b/frontend/src/components/objects/Connection.scss
@@ -0,0 +1,68 @@
+@import "../../colors";
+
+.connection {
+ border-top: 3px solid $color-primary-3;
+ border-bottom: 3px solid $color-primary-3;
+ background-color: $color-primary-0;
+
+ td {
+ vertical-align: middle;
+ }
+
+ .connection-service {
+ .btn {
+ width: 100%;
+ }
+
+ .btn:hover {
+ color: $color-primary-4;
+ background-color: $color-primary-1 !important;
+ }
+ }
+
+ .connection-icon {
+ font-size: 18px;
+ font-weight: 600;
+ margin-right: 6px;
+ cursor: pointer;
+ }
+
+ .connection-icon.icon-enabled {
+ color: $color-secondary-2;
+ }
+
+ &:hover {
+ background-color: $color-primary-2;
+ }
+
+ &.connection-selected {
+ background-color: $color-primary-2;
+ }
+
+ &.has-matched-rules {
+ border-bottom: 0;
+ }
+
+ .link-popover {
+ font-weight: 400;
+ }
+}
+
+.connection-popover {
+ border: none;
+ background-color: $color-primary-4;
+
+ .popover-body {
+ color: $color-primary-1;
+
+ textarea {
+ font-size: 12px;
+ width: 200px;
+ background-color: $color-primary-3;
+ }
+ }
+
+ .arrow::after {
+ border-right-color: $color-primary-4;
+ }
+}
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;
diff --git a/frontend/src/components/objects/ConnectionMatchedRules.scss b/frontend/src/components/objects/ConnectionMatchedRules.scss
new file mode 100644
index 0000000..f46a914
--- /dev/null
+++ b/frontend/src/components/objects/ConnectionMatchedRules.scss
@@ -0,0 +1,23 @@
+@import "../../colors";
+
+.connection-matches {
+ background-color: $color-primary-0;
+
+ .rule-buttons {
+ padding: 0;
+ }
+
+ .button-field {
+ display: inline-block;
+ margin-right: 5px;
+
+ button {
+ font-size: 0.8em;
+ padding: 2px 10px;
+ }
+ }
+
+ .row-label {
+ font-size: 0.8em;
+ }
+}
diff --git a/frontend/src/components/objects/MessageAction.js b/frontend/src/components/objects/MessageAction.js
new file mode 100644
index 0000000..9f199b7
--- /dev/null
+++ b/frontend/src/components/objects/MessageAction.js
@@ -0,0 +1,68 @@
+/*
+ * 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 './MessageAction.scss';
+import {Modal} from "react-bootstrap";
+import TextField from "../fields/TextField";
+import ButtonField from "../fields/ButtonField";
+
+class MessageAction extends Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ copyButtonText: "copy"
+ };
+ this.actionValue = React.createRef();
+ this.copyActionValue = this.copyActionValue.bind(this);
+ }
+
+ copyActionValue() {
+ this.actionValue.current.select();
+ document.execCommand('copy');
+ this.setState({copyButtonText: "copied!"});
+ setTimeout(() => this.setState({copyButtonText: "copy"}), 3000);
+ }
+
+ render() {
+ return (
+
+
+
+ {this.props.actionName}
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default MessageAction;
diff --git a/frontend/src/components/objects/MessageAction.scss b/frontend/src/components/objects/MessageAction.scss
new file mode 100644
index 0000000..996007b
--- /dev/null
+++ b/frontend/src/components/objects/MessageAction.scss
@@ -0,0 +1,8 @@
+@import "../../colors";
+
+.message-action-value {
+ font-size: 13px;
+ padding: 15px;
+ color: $color-primary-4;
+ background-color: $color-primary-2;
+}
diff --git a/frontend/src/components/pages/ConfigurationPage.js b/frontend/src/components/pages/ConfigurationPage.js
new file mode 100644
index 0000000..6ab8ae3
--- /dev/null
+++ b/frontend/src/components/pages/ConfigurationPage.js
@@ -0,0 +1,179 @@
+/*
+ * 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 '../panels/common.scss';
+import './ConfigurationPage.scss';
+import LinkPopover from "../objects/LinkPopover";
+import {Col, Container, Row} from "react-bootstrap";
+import InputField from "../fields/InputField";
+import TextField from "../fields/TextField";
+import ButtonField from "../fields/ButtonField";
+import CheckField from "../fields/CheckField";
+import {createCurlCommand} from "../../utils";
+import Table from "react-bootstrap/Table";
+import validation from "../../validation";
+import backend from "../../backend";
+
+class ConfigurationPage extends Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ settings: {
+ "config": {
+ "server_address": "",
+ "flag_regex": "",
+ "auth_required": false
+ },
+ "accounts": {
+ }
+ },
+ newUsername: "",
+ newPassword: ""
+ };
+ }
+
+ saveSettings = () => {
+ if (this.validateSettings(this.state.settings)) {
+ backend.post("/setup", this.state.settings).then(_ => {
+ this.props.onConfigured();
+ }).catch(res => {
+ this.setState({setupStatusCode: res.status, setupResponse: JSON.stringify(res.json)});
+ });
+ }
+ };
+
+ validateSettings = (settings) => {
+ let valid = true;
+ if (!validation.isValidAddress(settings.config.server_address, true)) {
+ this.setState({serverAddressError: "invalid ip_address"});
+ valid = false;
+ }
+ if (settings.config.flag_regex.length < 8) {
+ this.setState({flagRegexError: "flag_regex.length < 8"});
+ valid = false;
+ }
+
+ return valid;
+ };
+
+ updateParam = (callback) => {
+ callback(this.state.settings);
+ this.setState({settings: this.state.settings});
+ };
+
+ addAccount = () => {
+ if (this.state.newUsername.length !== 0 && this.state.newPassword.length !== 0) {
+ const settings = this.state.settings;
+ settings.accounts[this.state.newUsername] = this.state.newPassword;
+
+ this.setState({
+ newUsername: "",
+ newPassword: "",
+ settings: settings
+ });
+ } else {
+ this.setState({
+ newUsernameActive: this.state.newUsername.length === 0,
+ newPasswordActive: this.state.newPassword.length === 0
+ });
+ }
+ };
+
+ render() {
+ const settings = this.state.settings;
+ const curlCommand = createCurlCommand("/setup", "POST", settings);
+
+ const accounts = Object.entries(settings.accounts).map(([username, password]) =>
+
+ {username} |
+ |
+ this.updateParam((s) => delete s.accounts[username]) }/> |
+ ).concat(
+ this.setState({newUsername: v})} /> |
+ this.setState({newPassword: v})} /> |
+ |
+ );
+
+ return (
+
+
+
+
+
+ POST /setup
+
+
+
+
+
+
+
+ this.updateParam((s) => s.config.server_address = v)} />
+ this.updateParam((s) => s.config.flag_regex = v)}
+ error={this.state.flagRegexError} />
+
+ this.updateParam((s) => s.config.auth_required = v)}/>
+
+
+
+
+
+ accounts:
+
+
+
+
+ username |
+ password |
+ actions |
+
+
+
+ {accounts}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default ConfigurationPage;
diff --git a/frontend/src/components/pages/ConfigurationPage.scss b/frontend/src/components/pages/ConfigurationPage.scss
new file mode 100644
index 0000000..4509865
--- /dev/null
+++ b/frontend/src/components/pages/ConfigurationPage.scss
@@ -0,0 +1,18 @@
+@import "../../colors";
+
+.configuration-page {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ background-color: $color-primary-0;
+
+ .pane {
+ flex-basis: 900px;
+ margin-bottom: 200px;
+ }
+
+ .pane-container {
+ padding-bottom: 1px;
+ }
+}
diff --git a/frontend/src/components/pages/MainPage.js b/frontend/src/components/pages/MainPage.js
new file mode 100644
index 0000000..7376091
--- /dev/null
+++ b/frontend/src/components/pages/MainPage.js
@@ -0,0 +1,76 @@
+/*
+ * 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 './MainPage.scss';
+import './common.scss';
+import Connections from "../panels/ConnectionsPane";
+import StreamsPane from "../panels/StreamsPane";
+import {BrowserRouter as Router, Route, Switch} from "react-router-dom";
+import Timeline from "../Timeline";
+import PcapsPane from "../panels/PcapsPane";
+import RulesPane from "../panels/RulesPane";
+import ServicesPane from "../panels/ServicesPane";
+import Header from "../Header";
+import Filters from "../dialogs/Filters";
+import MainPane from "../panels/MainPane";
+
+class MainPage extends Component {
+
+ state = {};
+
+ render() {
+ let modal;
+ if (this.state.filterWindowOpen) {
+ modal = this.setState({filterWindowOpen: false})}/>;
+ }
+
+ return (
+
+
+
+ this.setState({filterWindowOpen: true})}/>
+
+
+
+
+ this.setState({selectedConnection: c})}/>
+
+
+
+ }/>
+ }/>
+ }/>
+ }/>
+ }/>
+
+
+
+ {modal}
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default MainPage;
diff --git a/frontend/src/components/pages/MainPage.scss b/frontend/src/components/pages/MainPage.scss
new file mode 100644
index 0000000..3b1a689
--- /dev/null
+++ b/frontend/src/components/pages/MainPage.scss
@@ -0,0 +1,24 @@
+@import "../../colors";
+
+.main-page {
+ .page-content {
+ display: flex;
+ flex: 1;
+ padding: 0 15px;
+ background-color: $color-primary-2;
+
+ .connections-pane {
+ flex: 1 0;
+ margin-right: 7.5px;
+ }
+
+ .details-pane {
+ flex: 1 1;
+ margin-left: 7.5px;
+ }
+ }
+
+ .page-footer {
+ flex: 0;
+ }
+}
diff --git a/frontend/src/components/pages/ServiceUnavailablePage.js b/frontend/src/components/pages/ServiceUnavailablePage.js
new file mode 100644
index 0000000..f27d84d
--- /dev/null
+++ b/frontend/src/components/pages/ServiceUnavailablePage.js
@@ -0,0 +1,34 @@
+/*
+ * 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 './MainPage.scss';
+
+class ServiceUnavailablePage extends Component {
+
+ state = {};
+
+ render() {
+ return (
+
+
+
+ );
+ }
+}
+
+export default ServiceUnavailablePage;
diff --git a/frontend/src/components/pages/common.scss b/frontend/src/components/pages/common.scss
new file mode 100644
index 0000000..fcf5c20
--- /dev/null
+++ b/frontend/src/components/pages/common.scss
@@ -0,0 +1,16 @@
+.page {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ height: 100vh;
+
+ .page-header,
+ .page-footer {
+ flex: 0;
+ }
+
+ .page-content {
+ overflow: hidden;
+ flex: 1;
+ }
+}
diff --git a/frontend/src/components/panels/ConfigurationPane.js b/frontend/src/components/panels/ConfigurationPane.js
deleted file mode 100644
index 9ae2cfb..0000000
--- a/frontend/src/components/panels/ConfigurationPane.js
+++ /dev/null
@@ -1,179 +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 './common.scss';
-import './ConfigurationPane.scss';
-import LinkPopover from "../objects/LinkPopover";
-import {Col, Container, Row} from "react-bootstrap";
-import InputField from "../fields/InputField";
-import TextField from "../fields/TextField";
-import ButtonField from "../fields/ButtonField";
-import CheckField from "../fields/CheckField";
-import {createCurlCommand} from "../../utils";
-import Table from "react-bootstrap/Table";
-import validation from "../../validation";
-import backend from "../../backend";
-
-class ConfigurationPane extends Component {
-
- constructor(props) {
- super(props);
- this.state = {
- settings: {
- "config": {
- "server_address": "",
- "flag_regex": "",
- "auth_required": false
- },
- "accounts": {
- }
- },
- newUsername: "",
- newPassword: ""
- };
- }
-
- saveSettings = () => {
- if (this.validateSettings(this.state.settings)) {
- backend.post("/setup", this.state.settings).then(_ => {
- this.props.onConfigured();
- }).catch(res => {
- this.setState({setupStatusCode: res.status, setupResponse: JSON.stringify(res.json)});
- });
- }
- };
-
- validateSettings = (settings) => {
- let valid = true;
- if (!validation.isValidAddress(settings.config.server_address, true)) {
- this.setState({serverAddressError: "invalid ip_address"});
- valid = false;
- }
- if (settings.config.flag_regex.length < 8) {
- this.setState({flagRegexError: "flag_regex.length < 8"});
- valid = false;
- }
-
- return valid;
- };
-
- updateParam = (callback) => {
- callback(this.state.settings);
- this.setState({settings: this.state.settings});
- };
-
- addAccount = () => {
- if (this.state.newUsername.length !== 0 && this.state.newPassword.length !== 0) {
- const settings = this.state.settings;
- settings.accounts[this.state.newUsername] = this.state.newPassword;
-
- this.setState({
- newUsername: "",
- newPassword: "",
- settings: settings
- });
- } else {
- this.setState({
- newUsernameActive: this.state.newUsername.length === 0,
- newPasswordActive: this.state.newPassword.length === 0
- });
- }
- };
-
- render() {
- const settings = this.state.settings;
- const curlCommand = createCurlCommand("/setup", "POST", settings);
-
- const accounts = Object.entries(settings.accounts).map(([username, password]) =>
-
- {username} |
- |
- this.updateParam((s) => delete s.accounts[username]) }/> |
- ).concat(
- this.setState({newUsername: v})} /> |
- this.setState({newPassword: v})} /> |
- |
- );
-
- return (
-
-
-
-
-
- POST /setup
-
-
-
-
-
-
-
- this.updateParam((s) => s.config.server_address = v)} />
- this.updateParam((s) => s.config.flag_regex = v)}
- error={this.state.flagRegexError} />
-
- this.updateParam((s) => s.config.auth_required = v)}/>
-
-
-
-
-
- accounts:
-
-
-
-
- username |
- password |
- actions |
-
-
-
- {accounts}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
- }
-}
-
-export default ConfigurationPane;
diff --git a/frontend/src/components/panels/ConfigurationPane.scss b/frontend/src/components/panels/ConfigurationPane.scss
deleted file mode 100644
index ef48b34..0000000
--- a/frontend/src/components/panels/ConfigurationPane.scss
+++ /dev/null
@@ -1,18 +0,0 @@
-@import "../../colors";
-
-.configuration-pane {
- display: flex;
- align-items: center;
- justify-content: center;
- height: 100%;
- background-color: $color-primary-0;
-
- .pane {
- flex-basis: 900px;
- margin-bottom: 200px;
- }
-
- .pane-container {
- padding-bottom: 1px;
- }
-}
diff --git a/frontend/src/components/panels/ConnectionsPane.js b/frontend/src/components/panels/ConnectionsPane.js
new file mode 100644
index 0000000..038ef8f
--- /dev/null
+++ b/frontend/src/components/panels/ConnectionsPane.js
@@ -0,0 +1,304 @@
+/*
+ * 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 './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";
+
+class ConnectionsPane extends Component {
+
+ state = {
+ loading: false,
+ connections: [],
+ firstConnection: null,
+ lastConnection: null,
+ };
+
+ constructor(props) {
+ super(props);
+
+ this.scrollTopThreashold = 0.00001;
+ this.scrollBottomThreashold = 0.99999;
+ this.maxConnections = 200;
+ this.queryLimit = 50;
+ this.connectionsListRef = React.createRef();
+ this.lastScrollPosition = 0;
+ }
+
+ componentDidMount() {
+ const initialParams = {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;
+ backend.get(`/api/connections/${id}`)
+ .then(res => this.connectionSelected(res.json, false))
+ .catch(error => log.error("Error loading initial connection", error));
+ }
+
+ this.loadConnections(initialParams, true).then(() => log.debug("Connections loaded"));
+
+ dispatcher.register("timeline_updates", 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("notifications", 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"));
+ }
+ });
+ }
+
+ connectionSelected = (c, doRedirect = true) => {
+ this.doSelectedConnectionRedirect = doRedirect;
+ 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;
+ return;
+ }
+
+ let relativeScroll = e.currentTarget.scrollTop / (e.currentTarget.scrollHeight - e.currentTarget.clientHeight);
+ if (!this.state.loading && relativeScroll > this.scrollBottomThreashold) {
+ this.loadConnections({from: this.state.lastConnection.id, limit: this.queryLimit,})
+ .then(() => log.info("Following connections loaded"));
+ }
+ if (!this.state.loading && relativeScroll < this.scrollTopThreashold) {
+ this.loadConnections({to: this.state.firstConnection.id, limit: this.queryLimit,})
+ .then(() => log.info("Previous connections loaded"));
+ if (this.state.showMoreRecentButton) {
+ this.setState({showMoreRecentButton: false});
+ }
+ } else {
+ if (this.lastScrollPosition > e.currentTarget.scrollTop) {
+ if (!this.state.showMoreRecentButton) {
+ this.setState({showMoreRecentButton: true});
+ }
+ } else {
+ if (this.state.showMoreRecentButton) {
+ this.setState({showMoreRecentButton: false});
+ }
+ }
+ }
+ 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(params, isInitial = false) {
+ const urlParams = new URLSearchParams(this.props.location.search);
+ for (const [name, value] of Object.entries(params)) {
+ urlParams.set(name, value);
+ }
+
+ this.setState({loading: true});
+ if (!this.state.rules) {
+ await this.loadRules();
+ }
+ if (!this.state.services) {
+ await this.loadServices();
+ }
+
+ let res = (await backend.get(`/api/connections?${urlParams}`)).json;
+
+ let connections = this.state.connections;
+ let firstConnection = this.state.firstConnection;
+ let lastConnection = this.state.lastConnection;
+
+ if (params !== undefined && params.from !== undefined && params.to === undefined) {
+ if (res.length > 0) {
+ if (!isInitial) {
+ res = res.slice(1);
+ }
+ connections = this.state.connections.concat(res);
+ lastConnection = connections[connections.length - 1];
+ if (isInitial) {
+ firstConnection = connections[0];
+ }
+ if (connections.length > this.maxConnections) {
+ connections = connections.slice(connections.length - this.maxConnections,
+ connections.length - 1);
+ firstConnection = connections[0];
+ }
+ }
+ } else if (params !== undefined && params.to !== undefined && params.from === undefined) {
+ if (res.length > 0) {
+ connections = res.slice(0, res.length - 1).concat(this.state.connections);
+ firstConnection = connections[0];
+ if (connections.length > this.maxConnections) {
+ connections = connections.slice(0, this.maxConnections);
+ lastConnection = connections[this.maxConnections - 1];
+ }
+ }
+ } else {
+ if (res.length > 0) {
+ connections = res;
+ firstConnection = connections[0];
+ lastConnection = connections[connections.length - 1];
+ } else {
+ connections = [];
+ firstConnection = null;
+ lastConnection = null;
+ }
+ }
+
+ this.setState({
+ loading: false,
+ connections: connections,
+ firstConnection: firstConnection,
+ lastConnection: lastConnection
+ });
+
+ if (firstConnection != null && lastConnection != null) {
+ dispatcher.dispatch("connection_updates", {
+ from: new Date(lastConnection["started_at"]),
+ to: new Date(firstConnection["started_at"])
+ });
+ }
+ }
+
+ loadRules = async () => {
+ return backend.get("/api/rules").then(res => this.setState({rules: res.json}));
+ };
+
+ loadServices = async () => {
+ return backend.get("/api/services").then(res => this.setState({services: res.json}));
+ };
+
+ render() {
+ let redirect;
+ if (this.doSelectedConnectionRedirect) {
+ redirect = ;
+ this.doSelectedConnectionRedirect = false;
+ } else if (this.doQueryStringRedirect) {
+ redirect = ;
+ this.doQueryStringRedirect = false;
+ }
+
+ let loading = null;
+ if (this.state.loading) {
+ loading =
+ Loading... |
+ ;
+ }
+
+ return (
+
+ {this.state.showMoreRecentButton &&
+ {
+ this.disableScrollHandler = true;
+ this.connectionsListRef.current.scrollTop = 0;
+ this.loadConnections({limit: this.queryLimit})
+ .then(() => {
+ this.disableScrollHandler = false;
+ log.info("Most recent connections loaded");
+ });
+ }}/>
+ }
+
+
+
+
+
+ service |
+ srcip |
+ srcport |
+ dstip |
+ dstport |
+ started_at |
+ duration |
+ up |
+ down |
+ actions |
+
+
+
+ {
+ 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}
+ addServicePortFilter={this.addServicePortFilter}
+ services={this.state.services}/>,
+ c.matched_rules.length > 0 &&
+
+ ];
+ })
+ }
+ {loading}
+
+
+
+ {redirect}
+
+
+ );
+ }
+
+}
+
+export default withRouter(ConnectionsPane);
diff --git a/frontend/src/components/panels/ConnectionsPane.scss b/frontend/src/components/panels/ConnectionsPane.scss
new file mode 100644
index 0000000..06f5827
--- /dev/null
+++ b/frontend/src/components/panels/ConnectionsPane.scss
@@ -0,0 +1,38 @@
+@import "../../colors";
+
+.connections-container {
+ position: relative;
+ height: 100%;
+ background-color: $color-primary-3;
+
+ .connections {
+ position: relative;
+ overflow-y: scroll;
+ height: 100%;
+
+ .table {
+ margin-bottom: 0;
+ }
+
+ th {
+ font-size: 13.5px;
+ position: sticky;
+ top: 0;
+ padding: 5px;
+ border: none;
+ background-color: $color-primary-3;
+ }
+
+ &:hover::-webkit-scrollbar-thumb {
+ background: $color-secondary-2;
+ }
+ }
+
+ .most-recent-button {
+ position: absolute;
+ z-index: 20;
+ top: 45px;
+ left: calc(50% - 50px);
+ background-color: red;
+ }
+}
diff --git a/frontend/src/components/panels/MainPane.js b/frontend/src/components/panels/MainPane.js
index d34d58a..74c859c 100644
--- a/frontend/src/components/panels/MainPane.js
+++ b/frontend/src/components/panels/MainPane.js
@@ -17,57 +17,22 @@
import React, {Component} from 'react';
import './common.scss';
-import './MainPane.scss';
-import Connections from "../../views/Connections";
-import ConnectionContent from "../ConnectionContent";
-import {Route, Switch, withRouter} from "react-router-dom";
-import PcapPane from "./PcapPane";
-import backend from "../../backend";
-import RulePane from "./RulePane";
-import ServicePane from "./ServicePane";
-import log from "../../log";
+import './ServicesPane.scss';
class MainPane extends Component {
state = {};
- componentDidMount() {
- const match = this.props.location.pathname.match(/^\/connections\/([a-f0-9]{24})$/);
- if (match != null) {
- this.loading = true;
- backend.get(`/api/connections/${match[1]}`)
- .then(res => {
- this.loading = false;
- this.setState({selectedConnection: res.json});
- log.debug(`Initial connection ${match[1]} loaded`);
- })
- .catch(error => log.error("Error loading initial connection", error));
- }
- }
-
render() {
return (
-
-
- {
- !this.loading &&
- this.setState({selectedConnection: c})}
- initialConnection={this.state.selectedConnection}/>
- }
-
-
-
- }/>
- }/>
- }/>
- }/>
- }/>
-
+
);
}
+
}
-export default withRouter(MainPane);
+export default MainPane;
diff --git a/frontend/src/components/panels/MainPane.scss b/frontend/src/components/panels/MainPane.scss
index 2973c00..c8460f2 100644
--- a/frontend/src/components/panels/MainPane.scss
+++ b/frontend/src/components/panels/MainPane.scss
@@ -1,22 +1,5 @@
@import "../../colors";
.main-pane {
- display: flex;
- height: 100%;
- padding: 0 15px;
- background-color: $color-primary-2;
- .pane {
- flex: 1;
- }
-
- .connections-pane {
- flex: 1 0;
- margin-right: 7.5px;
- }
-
- .details-pane {
- flex: 1 1;
- margin-left: 7.5px;
- }
}
diff --git a/frontend/src/components/panels/PcapPane.js b/frontend/src/components/panels/PcapPane.js
deleted file mode 100644
index d5c2225..0000000
--- a/frontend/src/components/panels/PcapPane.js
+++ /dev/null
@@ -1,273 +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 './PcapPane.scss';
-import './common.scss';
-import Table from "react-bootstrap/Table";
-import backend from "../../backend";
-import {createCurlCommand, dateTimeToTime, durationBetween, formatSize} from "../../utils";
-import InputField from "../fields/InputField";
-import CheckField from "../fields/CheckField";
-import TextField from "../fields/TextField";
-import ButtonField from "../fields/ButtonField";
-import LinkPopover from "../objects/LinkPopover";
-import dispatcher from "../../dispatcher";
-
-class PcapPane extends Component {
-
- state = {
- sessions: [],
- isUploadFileValid: true,
- isUploadFileFocused: false,
- uploadFlushAll: false,
- isFileValid: true,
- isFileFocused: false,
- fileValue: "",
- processFlushAll: false,
- deleteOriginalFile: false
- };
-
- componentDidMount() {
- this.loadSessions();
-
- dispatcher.register("notifications", payload => {
- if (payload.event === "pcap.upload" || payload.event === "pcap.file") {
- this.loadSessions();
- }
- });
-
- document.title = "caronte:~/pcaps$";
- }
-
- loadSessions = () => {
- backend.get("/api/pcap/sessions")
- .then(res => this.setState({sessions: res.json, sessionsStatusCode: res.status}))
- .catch(res => this.setState({
- sessions: res.json, sessionsStatusCode: res.status,
- sessionsResponse: JSON.stringify(res.json)
- }));
- };
-
- uploadPcap = () => {
- if (this.state.uploadSelectedFile == null || !this.state.isUploadFileValid) {
- this.setState({isUploadFileFocused: true});
- return;
- }
-
- const formData = new FormData();
- formData.append("file", this.state.uploadSelectedFile);
- formData.append("flush_all", this.state.uploadFlushAll);
- backend.postFile("/api/pcap/upload", formData).then(res => {
- this.setState({
- uploadStatusCode: res.status,
- uploadResponse: JSON.stringify(res.json)
- });
- this.resetUpload();
- this.loadSessions();
- }).catch(res => this.setState({
- uploadStatusCode: res.status,
- uploadResponse: JSON.stringify(res.json)
- })
- );
- };
-
- processPcap = () => {
- if (this.state.fileValue === "" || !this.state.isFileValid) {
- this.setState({isFileFocused: true});
- return;
- }
-
- backend.post("/api/pcap/file", {
- file: this.state.fileValue,
- flush_all: this.state.processFlushAll,
- delete_original_file: this.state.deleteOriginalFile
- }).then(res => {
- this.setState({
- processStatusCode: res.status,
- processResponse: JSON.stringify(res.json)
- });
- this.resetProcess();
- this.loadSessions();
- }).catch(res => this.setState({
- processStatusCode: res.status,
- processResponse: JSON.stringify(res.json)
- })
- );
- };
-
- resetUpload = () => {
- this.setState({
- isUploadFileValid: true,
- isUploadFileFocused: false,
- uploadFlushAll: false,
- uploadSelectedFile: null
- });
- };
-
- resetProcess = () => {
- this.setState({
- isFileValid: true,
- isFileFocused: false,
- fileValue: "",
- processFlushAll: false,
- deleteOriginalFile: false,
- });
- };
-
- render() {
- let sessions = this.state.sessions.map(s =>
-
- {s["id"].substring(0, 8)} |
- {dateTimeToTime(s["started_at"])} |
- {durationBetween(s["started_at"], s["completed_at"])} |
- {formatSize(s["size"])} |
- {s["processed_packets"]} |
- {s["invalid_packets"]} |
- |
- download
- |
-
- );
-
- const handleUploadFileChange = (file) => {
- this.setState({
- isUploadFileValid: file == null || (file.type.endsWith("pcap") || file.type.endsWith("pcapng")),
- isUploadFileFocused: false,
- uploadSelectedFile: file,
- uploadStatusCode: null,
- uploadResponse: null
- });
- };
-
- const handleFileChange = (file) => {
- this.setState({
- isFileValid: (file.endsWith("pcap") || file.endsWith("pcapng")),
- isFileFocused: false,
- fileValue: file,
- processStatusCode: null,
- processResponse: null
- });
- };
-
- const uploadCurlCommand = createCurlCommand("pcap/upload", "POST", null, {
- file: "@" + ((this.state.uploadSelectedFile != null && this.state.isUploadFileValid) ?
- this.state.uploadSelectedFile.name : "invalid.pcap"),
- flush_all: this.state.uploadFlushAll
- });
-
- const fileCurlCommand = createCurlCommand("pcap/file", "POST", {
- file: this.state.fileValue,
- flush_all: this.state.processFlushAll,
- delete_original_file: this.state.deleteOriginalFile
- });
-
- return (
-
-
-
- GET /api/pcap/sessions
-
-
-
-
-
-
-
-
- id |
- started_at |
- duration |
- size |
- processed_packets |
- invalid_packets |
- packets_per_service |
- actions |
-
-
-
- {sessions}
-
-
-
-
-
-
-
-
-
- POST /api/pcap/upload
-
-
-
-
-
-
-
- options:
- this.setState({uploadFlushAll: v})}/>
-
-
-
-
-
-
-
-
-
-
- POST /api/pcap/file
-
-
-
-
-
-
-
-
- this.setState({processFlushAll: v})}/>
- this.setState({deleteOriginalFile: v})}/>
-
-
-
-
-
-
-
-
-
- );
- }
-}
-
-export default PcapPane;
diff --git a/frontend/src/components/panels/PcapPane.scss b/frontend/src/components/panels/PcapPane.scss
deleted file mode 100644
index 4dbc2b2..0000000
--- a/frontend/src/components/panels/PcapPane.scss
+++ /dev/null
@@ -1,38 +0,0 @@
-@import "../../colors.scss";
-
-.pcap-pane {
- display: flex;
- flex-direction: column;
-
- .pcap-list {
- overflow: hidden;
- flex: 1;
-
- .section-content {
- height: 100%;
- }
-
- .section-table {
- height: calc(100% - 30px);
- }
-
- .table-cell-action {
- font-size: 13px;
- font-weight: 600;
- }
- }
-
- .upload-actions {
- display: flex;
- align-items: flex-end;
- margin-bottom: 20px;
- }
-
- .upload-options {
- flex: 1;
-
- span {
- font-size: 0.9em;
- }
- }
-}
diff --git a/frontend/src/components/panels/PcapsPane.js b/frontend/src/components/panels/PcapsPane.js
new file mode 100644
index 0000000..8722230
--- /dev/null
+++ b/frontend/src/components/panels/PcapsPane.js
@@ -0,0 +1,273 @@
+/*
+ * 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 './PcapsPane.scss';
+import './common.scss';
+import Table from "react-bootstrap/Table";
+import backend from "../../backend";
+import {createCurlCommand, dateTimeToTime, durationBetween, formatSize} from "../../utils";
+import InputField from "../fields/InputField";
+import CheckField from "../fields/CheckField";
+import TextField from "../fields/TextField";
+import ButtonField from "../fields/ButtonField";
+import LinkPopover from "../objects/LinkPopover";
+import dispatcher from "../../dispatcher";
+
+class PcapsPane extends Component {
+
+ state = {
+ sessions: [],
+ isUploadFileValid: true,
+ isUploadFileFocused: false,
+ uploadFlushAll: false,
+ isFileValid: true,
+ isFileFocused: false,
+ fileValue: "",
+ processFlushAll: false,
+ deleteOriginalFile: false
+ };
+
+ componentDidMount() {
+ this.loadSessions();
+
+ dispatcher.register("notifications", payload => {
+ if (payload.event === "pcap.upload" || payload.event === "pcap.file") {
+ this.loadSessions();
+ }
+ });
+
+ document.title = "caronte:~/pcaps$";
+ }
+
+ loadSessions = () => {
+ backend.get("/api/pcap/sessions")
+ .then(res => this.setState({sessions: res.json, sessionsStatusCode: res.status}))
+ .catch(res => this.setState({
+ sessions: res.json, sessionsStatusCode: res.status,
+ sessionsResponse: JSON.stringify(res.json)
+ }));
+ };
+
+ uploadPcap = () => {
+ if (this.state.uploadSelectedFile == null || !this.state.isUploadFileValid) {
+ this.setState({isUploadFileFocused: true});
+ return;
+ }
+
+ const formData = new FormData();
+ formData.append("file", this.state.uploadSelectedFile);
+ formData.append("flush_all", this.state.uploadFlushAll);
+ backend.postFile("/api/pcap/upload", formData).then(res => {
+ this.setState({
+ uploadStatusCode: res.status,
+ uploadResponse: JSON.stringify(res.json)
+ });
+ this.resetUpload();
+ this.loadSessions();
+ }).catch(res => this.setState({
+ uploadStatusCode: res.status,
+ uploadResponse: JSON.stringify(res.json)
+ })
+ );
+ };
+
+ processPcap = () => {
+ if (this.state.fileValue === "" || !this.state.isFileValid) {
+ this.setState({isFileFocused: true});
+ return;
+ }
+
+ backend.post("/api/pcap/file", {
+ file: this.state.fileValue,
+ flush_all: this.state.processFlushAll,
+ delete_original_file: this.state.deleteOriginalFile
+ }).then(res => {
+ this.setState({
+ processStatusCode: res.status,
+ processResponse: JSON.stringify(res.json)
+ });
+ this.resetProcess();
+ this.loadSessions();
+ }).catch(res => this.setState({
+ processStatusCode: res.status,
+ processResponse: JSON.stringify(res.json)
+ })
+ );
+ };
+
+ resetUpload = () => {
+ this.setState({
+ isUploadFileValid: true,
+ isUploadFileFocused: false,
+ uploadFlushAll: false,
+ uploadSelectedFile: null
+ });
+ };
+
+ resetProcess = () => {
+ this.setState({
+ isFileValid: true,
+ isFileFocused: false,
+ fileValue: "",
+ processFlushAll: false,
+ deleteOriginalFile: false,
+ });
+ };
+
+ render() {
+ let sessions = this.state.sessions.map(s =>
+
+ {s["id"].substring(0, 8)} |
+ {dateTimeToTime(s["started_at"])} |
+ {durationBetween(s["started_at"], s["completed_at"])} |
+ {formatSize(s["size"])} |
+ {s["processed_packets"]} |
+ {s["invalid_packets"]} |
+ |
+ download
+ |
+
+ );
+
+ const handleUploadFileChange = (file) => {
+ this.setState({
+ isUploadFileValid: file == null || (file.type.endsWith("pcap") || file.type.endsWith("pcapng")),
+ isUploadFileFocused: false,
+ uploadSelectedFile: file,
+ uploadStatusCode: null,
+ uploadResponse: null
+ });
+ };
+
+ const handleFileChange = (file) => {
+ this.setState({
+ isFileValid: (file.endsWith("pcap") || file.endsWith("pcapng")),
+ isFileFocused: false,
+ fileValue: file,
+ processStatusCode: null,
+ processResponse: null
+ });
+ };
+
+ const uploadCurlCommand = createCurlCommand("pcap/upload", "POST", null, {
+ file: "@" + ((this.state.uploadSelectedFile != null && this.state.isUploadFileValid) ?
+ this.state.uploadSelectedFile.name : "invalid.pcap"),
+ flush_all: this.state.uploadFlushAll
+ });
+
+ const fileCurlCommand = createCurlCommand("pcap/file", "POST", {
+ file: this.state.fileValue,
+ flush_all: this.state.processFlushAll,
+ delete_original_file: this.state.deleteOriginalFile
+ });
+
+ return (
+
+
+
+ GET /api/pcap/sessions
+
+
+
+
+
+
+
+
+ id |
+ started_at |
+ duration |
+ size |
+ processed_packets |
+ invalid_packets |
+ packets_per_service |
+ actions |
+
+
+
+ {sessions}
+
+
+
+
+
+
+
+
+
+ POST /api/pcap/upload
+
+
+
+
+
+
+
+ options:
+ this.setState({uploadFlushAll: v})}/>
+
+
+
+
+
+
+
+
+
+
+ POST /api/pcap/file
+
+
+
+
+
+
+
+
+ this.setState({processFlushAll: v})}/>
+ this.setState({deleteOriginalFile: v})}/>
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default PcapsPane;
diff --git a/frontend/src/components/panels/PcapsPane.scss b/frontend/src/components/panels/PcapsPane.scss
new file mode 100644
index 0000000..4dbc2b2
--- /dev/null
+++ b/frontend/src/components/panels/PcapsPane.scss
@@ -0,0 +1,38 @@
+@import "../../colors.scss";
+
+.pcap-pane {
+ display: flex;
+ flex-direction: column;
+
+ .pcap-list {
+ overflow: hidden;
+ flex: 1;
+
+ .section-content {
+ height: 100%;
+ }
+
+ .section-table {
+ height: calc(100% - 30px);
+ }
+
+ .table-cell-action {
+ font-size: 13px;
+ font-weight: 600;
+ }
+ }
+
+ .upload-actions {
+ display: flex;
+ align-items: flex-end;
+ margin-bottom: 20px;
+ }
+
+ .upload-options {
+ flex: 1;
+
+ span {
+ font-size: 0.9em;
+ }
+ }
+}
diff --git a/frontend/src/components/panels/RulePane.js b/frontend/src/components/panels/RulePane.js
deleted file mode 100644
index 9913962..0000000
--- a/frontend/src/components/panels/RulePane.js
+++ /dev/null
@@ -1,438 +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 './common.scss';
-import './RulePane.scss';
-import Table from "react-bootstrap/Table";
-import {Col, Container, Row} from "react-bootstrap";
-import InputField from "../fields/InputField";
-import CheckField from "../fields/CheckField";
-import TextField from "../fields/TextField";
-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 validation from "../../validation";
-import LinkPopover from "../objects/LinkPopover";
-import {randomClassName} from "../../utils";
-import dispatcher from "../../dispatcher";
-
-const classNames = require('classnames');
-const _ = require('lodash');
-
-class RulePane extends Component {
-
- emptyRule = {
- "name": "",
- "color": "",
- "notes": "",
- "enabled": true,
- "patterns": [],
- "filter": {
- "service_port": 0,
- "client_address": "",
- "client_port": 0,
- "min_duration": 0,
- "max_duration": 0,
- "min_bytes": 0,
- "max_bytes": 0
- },
- "version": 0
- };
- emptyPattern = {
- "regex": "",
- "flags": {
- "caseless": false,
- "dot_all": false,
- "multi_line": false,
- "utf_8_mode": false,
- "unicode_property": false
- },
- "min_occurrences": 0,
- "max_occurrences": 0,
- "direction": 0
- };
- state = {
- rules: [],
- newRule: this.emptyRule,
- newPattern: this.emptyPattern
- };
-
- constructor(props) {
- super(props);
-
- this.directions = {
- 0: "both",
- 1: "c->s",
- 2: "s->c"
- };
- }
-
- componentDidMount() {
- this.reset();
- this.loadRules();
-
- dispatcher.register("notifications", payload => {
- if (payload.event === "rules.new" || payload.event === "rules.edit") {
- this.loadRules();
- }
- });
-
- document.title = "caronte:~/rules$";
- }
-
- 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)}));
- };
-
- addRule = () => {
- if (this.validateRule(this.state.newRule)) {
- backend.post("/api/rules", this.state.newRule).then(res => {
- this.reset();
- this.setState({ruleStatusCode: res.status});
- this.loadRules();
- }).catch(res => {
- this.setState({ruleStatusCode: res.status, ruleResponse: JSON.stringify(res.json)});
- });
- }
- };
-
- updateRule = () => {
- const rule = this.state.selectedRule;
- if (this.validateRule(rule)) {
- backend.put(`/api/rules/${rule.id}`, rule).then(res => {
- this.reset();
- this.setState({ruleStatusCode: res.status});
- this.loadRules();
- }).catch(res => {
- this.setState({ruleStatusCode: res.status, ruleResponse: JSON.stringify(res.json)});
- });
- }
- };
-
- validateRule = (rule) => {
- let valid = true;
- if (rule.name.length < 3) {
- this.setState({ruleNameError: "name.length < 3"});
- valid = false;
- }
- if (!validation.isValidColor(rule.color)) {
- this.setState({ruleColorError: "color is not hexcolor"});
- valid = false;
- }
- if (!validation.isValidPort(rule.filter.service_port)) {
- this.setState({ruleServicePortError: "service_port > 65565"});
- valid = false;
- }
- if (!validation.isValidPort(rule.filter.client_port)) {
- this.setState({ruleClientPortError: "client_port > 65565"});
- valid = false;
- }
- if (!validation.isValidAddress(rule.filter.client_address)) {
- this.setState({ruleClientAddressError: "client_address is not ip_address"});
- valid = false;
- }
- if (rule.filter.min_duration > rule.filter.max_duration) {
- this.setState({ruleDurationError: "min_duration > max_dur."});
- valid = false;
- }
- if (rule.filter.min_bytes > rule.filter.max_bytes) {
- this.setState({ruleBytesError: "min_bytes > max_bytes"});
- valid = false;
- }
- if (rule.patterns.length < 1) {
- this.setState({rulePatternsError: "patterns.length < 1"});
- valid = false;
- }
-
- return valid;
- };
-
- reset = () => {
- const newRule = _.cloneDeep(this.emptyRule);
- const newPattern = _.cloneDeep(this.emptyPattern);
- this.setState({
- selectedRule: null,
- newRule: newRule,
- selectedPattern: null,
- newPattern: newPattern,
- patternRegexFocused: false,
- patternOccurrencesFocused: false,
- ruleNameError: null,
- ruleColorError: null,
- ruleServicePortError: null,
- ruleClientPortError: null,
- ruleClientAddressError: null,
- ruleDurationError: null,
- ruleBytesError: null,
- rulePatternsError: null,
- ruleStatusCode: null,
- rulesStatusCode: null,
- ruleResponse: null,
- rulesResponse: null
- });
- };
-
- updateParam = (callback) => {
- const updatedRule = this.currentRule();
- callback(updatedRule);
- this.setState({newRule: updatedRule});
- };
-
- currentRule = () => this.state.selectedRule != null ? this.state.selectedRule : this.state.newRule;
-
- addPattern = (pattern) => {
- if (!this.validatePattern(pattern)) {
- return;
- }
-
- const newPattern = _.cloneDeep(this.emptyPattern);
- this.currentRule().patterns.push(pattern);
- this.setState({
- newPattern: newPattern
- });
- };
-
- editPattern = (pattern) => {
- this.setState({
- selectedPattern: pattern
- });
- };
-
- updatePattern = (pattern) => {
- if (!this.validatePattern(pattern)) {
- return;
- }
-
- this.setState({
- selectedPattern: null
- });
- };
-
- validatePattern = (pattern) => {
- let valid = true;
- if (pattern.regex === "") {
- valid = false;
- this.setState({patternRegexFocused: true});
- }
- if (pattern.min_occurrences > pattern.max_occurrences) {
- valid = false;
- this.setState({patternOccurrencesFocused: true});
- }
- return valid;
- };
-
- render() {
- const isUpdate = this.state.selectedRule != null;
- const rule = this.currentRule();
- const pattern = this.state.selectedPattern || this.state.newPattern;
-
- let rules = this.state.rules.map(r =>
- {
- 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"]} |
-
- );
-
- let patterns = (this.state.selectedPattern == null && !isUpdate ?
- rule.patterns.concat(this.state.newPattern) :
- rule.patterns
- ).map(p => p === pattern ?
-
-
- {
- this.updateParam(() => pattern.regex = v);
- this.setState({patternRegexFocused: pattern.regex === ""});
- }}/>
- |
- this.updateParam(() => pattern.flags.caseless = v)}/> |
- this.updateParam(() => pattern.flags.dot_all = v)}/> |
- this.updateParam(() => pattern.flags.multi_line = v)}/> |
- this.updateParam(() => pattern.flags.utf_8_mode = v)}/> |
- this.updateParam(() => pattern.flags.unicode_property = v)}/> |
-
- this.updateParam(() => pattern.min_occurrences = v)}/>
- |
-
- this.updateParam(() => pattern.max_occurrences = v)}/>
- |
- s", "s->c"]}
- value={this.directions[pattern.direction]}
- onChange={(v) => this.updateParam(() => pattern.direction = v)}/> |
- {this.state.selectedPattern == null ?
- this.addPattern(p)}/> :
- this.updatePattern(p)}/>}
- |
-
- :
-
- {p.regex} |
- {p.flags.caseless ? "yes" : "no"} |
- {p.flags.dot_all ? "yes" : "no"} |
- {p.flags.multi_line ? "yes" : "no"} |
- {p.flags.utf_8_mode ? "yes" : "no"} |
- {p.flags.unicode_property ? "yes" : "no"} |
- {p.min_occurrences} |
- {p.max_occurrences} |
- {this.directions[p.direction]} |
- {!isUpdate && this.editPattern(p)}/> | }
-
- );
-
- return (
-
-
-
- GET /api/rules
- {this.state.rulesStatusCode &&
- }
-
-
-
-
-
-
-
- id |
- name |
- color |
- notes |
-
-
-
- {rules}
-
-
-
-
-
-
-
-
-
- {isUpdate ? `PUT /api/rules/${this.state.selectedRule.id}` : "POST /api/rules"}
-
-
-
-
-
-
-
-
- this.updateParam((r) => r.name = v)}
- error={this.state.ruleNameError}/>
- this.updateParam((r) => r.color = v)}/>
- this.updateParam((r) => r.notes = v)}/>
-
-
-
- filters:
- this.updateParam((r) => r.filter.service_port = v)}
- min={0} max={65565} error={this.state.ruleServicePortError}
- readonly={isUpdate}/>
- this.updateParam((r) => r.filter.client_port = v)}
- min={0} max={65565} error={this.state.ruleClientPortError}
- readonly={isUpdate}/>
- this.updateParam((r) => r.filter.client_address = v)}/>
-
-
-
- this.updateParam((r) => r.filter.min_duration = v)}/>
- this.updateParam((r) => r.filter.max_duration = v)}/>
- this.updateParam((r) => r.filter.min_bytes = v)}/>
- this.updateParam((r) => r.filter.max_bytes = v)}/>
-
-
-
-
-
-
-
-
- regex |
- !Aa |
- .* |
- \n+ |
- UTF8 |
- Uni_ |
- min |
- max |
- direction |
- {!isUpdate && actions | }
-
-
-
- {patterns}
-
-
- {this.state.rulePatternsError != null &&
- error: {this.state.rulePatternsError}}
-
-
-
-
- {}
-
-
-
-
- );
- }
-
-}
-
-export default RulePane;
diff --git a/frontend/src/components/panels/RulePane.scss b/frontend/src/components/panels/RulePane.scss
deleted file mode 100644
index 992445a..0000000
--- a/frontend/src/components/panels/RulePane.scss
+++ /dev/null
@@ -1,32 +0,0 @@
-
-.rule-pane {
- display: flex;
- flex-direction: column;
-
- .rules-list {
- overflow: hidden;
- flex: 2 1;
-
- .section-content {
- height: 100%;
- }
-
- .section-table {
- height: calc(100% - 30px);
- }
- }
-
- .rule-edit {
- display: flex;
- flex: 3 0;
- flex-direction: column;
-
- .section-content {
- flex: 1;
- }
-
- .section-table {
- max-height: 150px;
- }
- }
-}
diff --git a/frontend/src/components/panels/RulesPane.js b/frontend/src/components/panels/RulesPane.js
new file mode 100644
index 0000000..a66cde7
--- /dev/null
+++ b/frontend/src/components/panels/RulesPane.js
@@ -0,0 +1,438 @@
+/*
+ * 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 './common.scss';
+import './RulesPane.scss';
+import Table from "react-bootstrap/Table";
+import {Col, Container, Row} from "react-bootstrap";
+import InputField from "../fields/InputField";
+import CheckField from "../fields/CheckField";
+import TextField from "../fields/TextField";
+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 validation from "../../validation";
+import LinkPopover from "../objects/LinkPopover";
+import {randomClassName} from "../../utils";
+import dispatcher from "../../dispatcher";
+
+const classNames = require('classnames');
+const _ = require('lodash');
+
+class RulesPane extends Component {
+
+ emptyRule = {
+ "name": "",
+ "color": "",
+ "notes": "",
+ "enabled": true,
+ "patterns": [],
+ "filter": {
+ "service_port": 0,
+ "client_address": "",
+ "client_port": 0,
+ "min_duration": 0,
+ "max_duration": 0,
+ "min_bytes": 0,
+ "max_bytes": 0
+ },
+ "version": 0
+ };
+ emptyPattern = {
+ "regex": "",
+ "flags": {
+ "caseless": false,
+ "dot_all": false,
+ "multi_line": false,
+ "utf_8_mode": false,
+ "unicode_property": false
+ },
+ "min_occurrences": 0,
+ "max_occurrences": 0,
+ "direction": 0
+ };
+ state = {
+ rules: [],
+ newRule: this.emptyRule,
+ newPattern: this.emptyPattern
+ };
+
+ constructor(props) {
+ super(props);
+
+ this.directions = {
+ 0: "both",
+ 1: "c->s",
+ 2: "s->c"
+ };
+ }
+
+ componentDidMount() {
+ this.reset();
+ this.loadRules();
+
+ dispatcher.register("notifications", payload => {
+ if (payload.event === "rules.new" || payload.event === "rules.edit") {
+ this.loadRules();
+ }
+ });
+
+ document.title = "caronte:~/rules$";
+ }
+
+ 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)}));
+ };
+
+ addRule = () => {
+ if (this.validateRule(this.state.newRule)) {
+ backend.post("/api/rules", this.state.newRule).then(res => {
+ this.reset();
+ this.setState({ruleStatusCode: res.status});
+ this.loadRules();
+ }).catch(res => {
+ this.setState({ruleStatusCode: res.status, ruleResponse: JSON.stringify(res.json)});
+ });
+ }
+ };
+
+ updateRule = () => {
+ const rule = this.state.selectedRule;
+ if (this.validateRule(rule)) {
+ backend.put(`/api/rules/${rule.id}`, rule).then(res => {
+ this.reset();
+ this.setState({ruleStatusCode: res.status});
+ this.loadRules();
+ }).catch(res => {
+ this.setState({ruleStatusCode: res.status, ruleResponse: JSON.stringify(res.json)});
+ });
+ }
+ };
+
+ validateRule = (rule) => {
+ let valid = true;
+ if (rule.name.length < 3) {
+ this.setState({ruleNameError: "name.length < 3"});
+ valid = false;
+ }
+ if (!validation.isValidColor(rule.color)) {
+ this.setState({ruleColorError: "color is not hexcolor"});
+ valid = false;
+ }
+ if (!validation.isValidPort(rule.filter.service_port)) {
+ this.setState({ruleServicePortError: "service_port > 65565"});
+ valid = false;
+ }
+ if (!validation.isValidPort(rule.filter.client_port)) {
+ this.setState({ruleClientPortError: "client_port > 65565"});
+ valid = false;
+ }
+ if (!validation.isValidAddress(rule.filter.client_address)) {
+ this.setState({ruleClientAddressError: "client_address is not ip_address"});
+ valid = false;
+ }
+ if (rule.filter.min_duration > rule.filter.max_duration) {
+ this.setState({ruleDurationError: "min_duration > max_dur."});
+ valid = false;
+ }
+ if (rule.filter.min_bytes > rule.filter.max_bytes) {
+ this.setState({ruleBytesError: "min_bytes > max_bytes"});
+ valid = false;
+ }
+ if (rule.patterns.length < 1) {
+ this.setState({rulePatternsError: "patterns.length < 1"});
+ valid = false;
+ }
+
+ return valid;
+ };
+
+ reset = () => {
+ const newRule = _.cloneDeep(this.emptyRule);
+ const newPattern = _.cloneDeep(this.emptyPattern);
+ this.setState({
+ selectedRule: null,
+ newRule: newRule,
+ selectedPattern: null,
+ newPattern: newPattern,
+ patternRegexFocused: false,
+ patternOccurrencesFocused: false,
+ ruleNameError: null,
+ ruleColorError: null,
+ ruleServicePortError: null,
+ ruleClientPortError: null,
+ ruleClientAddressError: null,
+ ruleDurationError: null,
+ ruleBytesError: null,
+ rulePatternsError: null,
+ ruleStatusCode: null,
+ rulesStatusCode: null,
+ ruleResponse: null,
+ rulesResponse: null
+ });
+ };
+
+ updateParam = (callback) => {
+ const updatedRule = this.currentRule();
+ callback(updatedRule);
+ this.setState({newRule: updatedRule});
+ };
+
+ currentRule = () => this.state.selectedRule != null ? this.state.selectedRule : this.state.newRule;
+
+ addPattern = (pattern) => {
+ if (!this.validatePattern(pattern)) {
+ return;
+ }
+
+ const newPattern = _.cloneDeep(this.emptyPattern);
+ this.currentRule().patterns.push(pattern);
+ this.setState({
+ newPattern: newPattern
+ });
+ };
+
+ editPattern = (pattern) => {
+ this.setState({
+ selectedPattern: pattern
+ });
+ };
+
+ updatePattern = (pattern) => {
+ if (!this.validatePattern(pattern)) {
+ return;
+ }
+
+ this.setState({
+ selectedPattern: null
+ });
+ };
+
+ validatePattern = (pattern) => {
+ let valid = true;
+ if (pattern.regex === "") {
+ valid = false;
+ this.setState({patternRegexFocused: true});
+ }
+ if (pattern.min_occurrences > pattern.max_occurrences) {
+ valid = false;
+ this.setState({patternOccurrencesFocused: true});
+ }
+ return valid;
+ };
+
+ render() {
+ const isUpdate = this.state.selectedRule != null;
+ const rule = this.currentRule();
+ const pattern = this.state.selectedPattern || this.state.newPattern;
+
+ let rules = this.state.rules.map(r =>
+ {
+ 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"]} |
+
+ );
+
+ let patterns = (this.state.selectedPattern == null && !isUpdate ?
+ rule.patterns.concat(this.state.newPattern) :
+ rule.patterns
+ ).map(p => p === pattern ?
+
+
+ {
+ this.updateParam(() => pattern.regex = v);
+ this.setState({patternRegexFocused: pattern.regex === ""});
+ }}/>
+ |
+ this.updateParam(() => pattern.flags.caseless = v)}/> |
+ this.updateParam(() => pattern.flags.dot_all = v)}/> |
+ this.updateParam(() => pattern.flags.multi_line = v)}/> |
+ this.updateParam(() => pattern.flags.utf_8_mode = v)}/> |
+ this.updateParam(() => pattern.flags.unicode_property = v)}/> |
+
+ this.updateParam(() => pattern.min_occurrences = v)}/>
+ |
+
+ this.updateParam(() => pattern.max_occurrences = v)}/>
+ |
+ s", "s->c"]}
+ value={this.directions[pattern.direction]}
+ onChange={(v) => this.updateParam(() => pattern.direction = v)}/> |
+ {this.state.selectedPattern == null ?
+ this.addPattern(p)}/> :
+ this.updatePattern(p)}/>}
+ |
+
+ :
+
+ {p.regex} |
+ {p.flags.caseless ? "yes" : "no"} |
+ {p.flags.dot_all ? "yes" : "no"} |
+ {p.flags.multi_line ? "yes" : "no"} |
+ {p.flags.utf_8_mode ? "yes" : "no"} |
+ {p.flags.unicode_property ? "yes" : "no"} |
+ {p.min_occurrences} |
+ {p.max_occurrences} |
+ {this.directions[p.direction]} |
+ {!isUpdate && this.editPattern(p)}/> | }
+
+ );
+
+ return (
+
+
+
+ GET /api/rules
+ {this.state.rulesStatusCode &&
+ }
+
+
+
+
+
+
+
+ id |
+ name |
+ color |
+ notes |
+
+
+
+ {rules}
+
+
+
+
+
+
+
+
+
+ {isUpdate ? `PUT /api/rules/${this.state.selectedRule.id}` : "POST /api/rules"}
+
+
+
+
+
+
+
+
+ this.updateParam((r) => r.name = v)}
+ error={this.state.ruleNameError}/>
+ this.updateParam((r) => r.color = v)}/>
+ this.updateParam((r) => r.notes = v)}/>
+
+
+
+ filters:
+ this.updateParam((r) => r.filter.service_port = v)}
+ min={0} max={65565} error={this.state.ruleServicePortError}
+ readonly={isUpdate}/>
+ this.updateParam((r) => r.filter.client_port = v)}
+ min={0} max={65565} error={this.state.ruleClientPortError}
+ readonly={isUpdate}/>
+ this.updateParam((r) => r.filter.client_address = v)}/>
+
+
+
+ this.updateParam((r) => r.filter.min_duration = v)}/>
+ this.updateParam((r) => r.filter.max_duration = v)}/>
+ this.updateParam((r) => r.filter.min_bytes = v)}/>
+ this.updateParam((r) => r.filter.max_bytes = v)}/>
+
+
+
+
+
+
+
+
+ regex |
+ !Aa |
+ .* |
+ \n+ |
+ UTF8 |
+ Uni_ |
+ min |
+ max |
+ direction |
+ {!isUpdate && actions | }
+
+
+
+ {patterns}
+
+
+ {this.state.rulePatternsError != null &&
+ error: {this.state.rulePatternsError}}
+
+
+
+
+ {}
+
+
+
+
+ );
+ }
+
+}
+
+export default RulesPane;
diff --git a/frontend/src/components/panels/RulesPane.scss b/frontend/src/components/panels/RulesPane.scss
new file mode 100644
index 0000000..992445a
--- /dev/null
+++ b/frontend/src/components/panels/RulesPane.scss
@@ -0,0 +1,32 @@
+
+.rule-pane {
+ display: flex;
+ flex-direction: column;
+
+ .rules-list {
+ overflow: hidden;
+ flex: 2 1;
+
+ .section-content {
+ height: 100%;
+ }
+
+ .section-table {
+ height: calc(100% - 30px);
+ }
+ }
+
+ .rule-edit {
+ display: flex;
+ flex: 3 0;
+ flex-direction: column;
+
+ .section-content {
+ flex: 1;
+ }
+
+ .section-table {
+ max-height: 150px;
+ }
+ }
+}
diff --git a/frontend/src/components/panels/ServicePane.js b/frontend/src/components/panels/ServicePane.js
deleted file mode 100644
index fc7004b..0000000
--- a/frontend/src/components/panels/ServicePane.js
+++ /dev/null
@@ -1,212 +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 './common.scss';
-import './ServicePane.scss';
-import Table from "react-bootstrap/Table";
-import {Col, Container, Row} from "react-bootstrap";
-import InputField from "../fields/InputField";
-import TextField from "../fields/TextField";
-import backend from "../../backend";
-import NumericField from "../fields/extensions/NumericField";
-import ColorField from "../fields/extensions/ColorField";
-import ButtonField from "../fields/ButtonField";
-import validation from "../../validation";
-import LinkPopover from "../objects/LinkPopover";
-import {createCurlCommand} from "../../utils";
-import dispatcher from "../../dispatcher";
-
-const classNames = require('classnames');
-const _ = require('lodash');
-
-class ServicePane extends Component {
-
- emptyService = {
- "port": 0,
- "name": "",
- "color": "",
- "notes": ""
- };
-
- state = {
- services: [],
- currentService: this.emptyService,
- };
-
- componentDidMount() {
- this.reset();
- this.loadServices();
-
- dispatcher.register("notifications", payload => {
- if (payload.event === "services.edit") {
- this.loadServices();
- }
- });
-
- document.title = "caronte:~/services$";
- }
-
- 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)}));
- };
-
- updateService = () => {
- const service = this.state.currentService;
- if (this.validateService(service)) {
- backend.put("/api/services", service).then(res => {
- this.reset();
- this.setState({serviceStatusCode: res.status});
- this.loadServices();
- }).catch(res => {
- this.setState({serviceStatusCode: res.status, serviceResponse: JSON.stringify(res.json)});
- });
- }
- };
-
- validateService = (service) => {
- let valid = true;
- if (!validation.isValidPort(service.port, true)) {
- this.setState({servicePortError: "port < 0 || port > 65565"});
- valid = false;
- }
- if (service.name.length < 3) {
- this.setState({serviceNameError: "name.length < 3"});
- valid = false;
- }
- if (!validation.isValidColor(service.color)) {
- this.setState({serviceColorError: "color is not hexcolor"});
- valid = false;
- }
-
- return valid;
- };
-
- reset = () => {
- this.setState({
- isUpdate: false,
- currentService: _.cloneDeep(this.emptyService),
- servicePortError: null,
- serviceNameError: null,
- serviceColorError: null,
- serviceStatusCode: null,
- servicesStatusCode: null,
- serviceResponse: null,
- servicesResponse: null
- });
- };
-
- updateParam = (callback) => {
- callback(this.state.currentService);
- this.setState({currentService: this.state.currentService});
- };
-
- render() {
- const isUpdate = this.state.isUpdate;
- const service = this.state.currentService;
-
- let services = this.state.services.map(s =>
- {
- this.reset();
- this.setState({isUpdate: true, currentService: _.cloneDeep(s)});
- }} className={classNames("row-small", "row-clickable", {"row-selected": service.port === s.port })}>
- {s["port"]} |
- {s["name"]} |
- |
- {s["notes"]} |
-
- );
-
- const curlCommand = createCurlCommand("/services", "PUT", service);
-
- return (
-
-
-
- GET /api/services
- {this.state.servicesStatusCode &&
- }
-
-
-
-
-
-
-
- port |
- name |
- color |
- notes |
-
-
-
- {services}
-
-
-
-
-
-
-
-
- PUT /api/services
-
-
-
-
-
-
-
- this.updateParam((s) => s.port = v)}
- min={0} max={65565} error={this.state.servicePortError} />
- this.updateParam((s) => s.name = v)}
- error={this.state.serviceNameError} />
- this.updateParam((s) => s.color = v)} />
-
-
-
- this.updateParam((s) => s.notes = v)} />
-
-
-
-
-
-
-
-
- {}
-
-
-
-
- );
- }
-
-}
-
-export default ServicePane;
diff --git a/frontend/src/components/panels/ServicePane.scss b/frontend/src/components/panels/ServicePane.scss
deleted file mode 100644
index daf7e79..0000000
--- a/frontend/src/components/panels/ServicePane.scss
+++ /dev/null
@@ -1,22 +0,0 @@
-
-.service-pane {
- display: flex;
- flex-direction: column;
-
- .services-list {
- overflow: hidden;
- flex: 1;
-
- .section-content {
- height: 100%;
- }
-
- .section-table {
- height: calc(100% - 30px);
- }
- }
-
- .service-edit {
- flex: 0;
- }
-}
diff --git a/frontend/src/components/panels/ServicesPane.js b/frontend/src/components/panels/ServicesPane.js
new file mode 100644
index 0000000..bc82356
--- /dev/null
+++ b/frontend/src/components/panels/ServicesPane.js
@@ -0,0 +1,212 @@
+/*
+ * 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 './common.scss';
+import './ServicesPane.scss';
+import Table from "react-bootstrap/Table";
+import {Col, Container, Row} from "react-bootstrap";
+import InputField from "../fields/InputField";
+import TextField from "../fields/TextField";
+import backend from "../../backend";
+import NumericField from "../fields/extensions/NumericField";
+import ColorField from "../fields/extensions/ColorField";
+import ButtonField from "../fields/ButtonField";
+import validation from "../../validation";
+import LinkPopover from "../objects/LinkPopover";
+import {createCurlCommand} from "../../utils";
+import dispatcher from "../../dispatcher";
+
+const classNames = require('classnames');
+const _ = require('lodash');
+
+class ServicesPane extends Component {
+
+ emptyService = {
+ "port": 0,
+ "name": "",
+ "color": "",
+ "notes": ""
+ };
+
+ state = {
+ services: [],
+ currentService: this.emptyService,
+ };
+
+ componentDidMount() {
+ this.reset();
+ this.loadServices();
+
+ dispatcher.register("notifications", payload => {
+ if (payload.event === "services.edit") {
+ this.loadServices();
+ }
+ });
+
+ document.title = "caronte:~/services$";
+ }
+
+ 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)}));
+ };
+
+ updateService = () => {
+ const service = this.state.currentService;
+ if (this.validateService(service)) {
+ backend.put("/api/services", service).then(res => {
+ this.reset();
+ this.setState({serviceStatusCode: res.status});
+ this.loadServices();
+ }).catch(res => {
+ this.setState({serviceStatusCode: res.status, serviceResponse: JSON.stringify(res.json)});
+ });
+ }
+ };
+
+ validateService = (service) => {
+ let valid = true;
+ if (!validation.isValidPort(service.port, true)) {
+ this.setState({servicePortError: "port < 0 || port > 65565"});
+ valid = false;
+ }
+ if (service.name.length < 3) {
+ this.setState({serviceNameError: "name.length < 3"});
+ valid = false;
+ }
+ if (!validation.isValidColor(service.color)) {
+ this.setState({serviceColorError: "color is not hexcolor"});
+ valid = false;
+ }
+
+ return valid;
+ };
+
+ reset = () => {
+ this.setState({
+ isUpdate: false,
+ currentService: _.cloneDeep(this.emptyService),
+ servicePortError: null,
+ serviceNameError: null,
+ serviceColorError: null,
+ serviceStatusCode: null,
+ servicesStatusCode: null,
+ serviceResponse: null,
+ servicesResponse: null
+ });
+ };
+
+ updateParam = (callback) => {
+ callback(this.state.currentService);
+ this.setState({currentService: this.state.currentService});
+ };
+
+ render() {
+ const isUpdate = this.state.isUpdate;
+ const service = this.state.currentService;
+
+ let services = this.state.services.map(s =>
+ {
+ this.reset();
+ this.setState({isUpdate: true, currentService: _.cloneDeep(s)});
+ }} className={classNames("row-small", "row-clickable", {"row-selected": service.port === s.port })}>
+ {s["port"]} |
+ {s["name"]} |
+ |
+ {s["notes"]} |
+
+ );
+
+ const curlCommand = createCurlCommand("/services", "PUT", service);
+
+ return (
+
+
+
+ GET /api/services
+ {this.state.servicesStatusCode &&
+ }
+
+
+
+
+
+
+
+ port |
+ name |
+ color |
+ notes |
+
+
+
+ {services}
+
+
+
+
+
+
+
+
+ PUT /api/services
+
+
+
+
+
+
+
+ this.updateParam((s) => s.port = v)}
+ min={0} max={65565} error={this.state.servicePortError} />
+ this.updateParam((s) => s.name = v)}
+ error={this.state.serviceNameError} />
+ this.updateParam((s) => s.color = v)} />
+
+
+
+ this.updateParam((s) => s.notes = v)} />
+
+
+
+
+
+
+
+
+ {}
+
+
+
+
+ );
+ }
+
+}
+
+export default ServicesPane;
diff --git a/frontend/src/components/panels/ServicesPane.scss b/frontend/src/components/panels/ServicesPane.scss
new file mode 100644
index 0000000..daf7e79
--- /dev/null
+++ b/frontend/src/components/panels/ServicesPane.scss
@@ -0,0 +1,22 @@
+
+.service-pane {
+ display: flex;
+ flex-direction: column;
+
+ .services-list {
+ overflow: hidden;
+ flex: 1;
+
+ .section-content {
+ height: 100%;
+ }
+
+ .section-table {
+ height: calc(100% - 30px);
+ }
+ }
+
+ .service-edit {
+ flex: 0;
+ }
+}
diff --git a/frontend/src/components/panels/StreamsPane.js b/frontend/src/components/panels/StreamsPane.js
new file mode 100644
index 0000000..c8bd121
--- /dev/null
+++ b/frontend/src/components/panels/StreamsPane.js
@@ -0,0 +1,242 @@
+/*
+ * 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 './StreamsPane.scss';
+import {Row} from 'react-bootstrap';
+import MessageAction from "../objects/MessageAction";
+import backend from "../../backend";
+import ButtonField from "../fields/ButtonField";
+import ChoiceField from "../fields/ChoiceField";
+import DOMPurify from 'dompurify';
+import ReactJson from 'react-json-view'
+import {downloadBlob, getHeaderValue} from "../../utils";
+import log from "../../log";
+
+const classNames = require('classnames');
+
+class StreamsPane extends Component {
+
+ state = {
+ messages: [],
+ format: "default",
+ tryParse: true
+ };
+
+ constructor(props) {
+ super(props);
+
+ this.validFormats = ["default", "hex", "hexdump", "base32", "base64", "ascii", "binary", "decimal", "octal"];
+ }
+
+ componentDidMount() {
+ if (this.props.connection && this.state.currentId !== this.props.connection.id) {
+ this.setState({currentId: this.props.connection.id});
+ this.loadStream(this.props.connection.id);
+ }
+
+ document.title = "caronte:~/$";
+ }
+
+ componentDidUpdate(prevProps, prevState, snapshot) {
+ if (this.props.connection && (
+ this.props.connection !== prevProps.connection || this.state.format !== prevState.format)) {
+ this.closeRenderWindow();
+ this.loadStream(this.props.connection.id);
+ }
+ }
+
+ componentWillUnmount() {
+ this.closeRenderWindow();
+ }
+
+ loadStream = (connectionId) => {
+ this.setState({messages: []});
+ backend.get(`/api/streams/${connectionId}?format=${this.state.format}`)
+ .then(res => this.setState({messages: res.json}));
+ };
+
+ setFormat = (format) => {
+ if (this.validFormats.includes(format)) {
+ this.setState({format: format});
+ }
+ };
+
+ tryParseConnectionMessage = (connectionMessage) => {
+ if (connectionMessage.metadata == null) {
+ return connectionMessage.content;
+ }
+ if (connectionMessage["is_metadata_continuation"]) {
+ return **already parsed in previous messages**;
+ }
+
+ let unrollMap = (obj) => obj == null ? null : Object.entries(obj).map(([key, value]) =>
+ {key}: {value}
+ );
+
+ let m = connectionMessage.metadata;
+ switch (m.type) {
+ case "http-request":
+ let url = {m.host}{m.url};
+ return
+ {m.method} {url} {m.protocol}
+ {unrollMap(m.headers)}
+ {m.body}
+ {unrollMap(m.trailers)}
+ ;
+ case "http-response":
+ const contentType = getHeaderValue(m, "Content-Type");
+ let body = m.body;
+ if (contentType && contentType.includes("application/json")) {
+ try {
+ const json = JSON.parse(m.body);
+ body = ;
+ } catch (e) {
+ console.log(e);
+ }
+ }
+
+ return
+ {m.protocol} {m.status}
+ {unrollMap(m.headers)}
+ {body}
+ {unrollMap(m.trailers)}
+ ;
+ default:
+ return connectionMessage.content;
+ }
+ };
+
+ connectionsActions = (connectionMessage) => {
+ if (!connectionMessage.metadata) {
+ return null;
+ }
+
+ const m = connectionMessage.metadata;
+ switch (m.type) {
+ case "http-request" :
+ if (!connectionMessage.metadata["reproducers"]) {
+ return;
+ }
+ return Object.entries(connectionMessage.metadata["reproducers"]).map(([actionName, actionValue]) =>
+ {
+ this.setState({
+ messageActionDialog: this.setState({messageActionDialog: null})}/>
+ });
+ }}/>
+ );
+ case "http-response":
+ const contentType = getHeaderValue(m, "Content-Type");
+
+ if (contentType && contentType.includes("text/html")) {
+ return {
+ let w;
+ if (this.state.renderWindow && !this.state.renderWindow.closed) {
+ w = this.state.renderWindow;
+ } else {
+ w = window.open("", "", "width=900, height=600, scrollbars=yes");
+ this.setState({renderWindow: w});
+ }
+ w.document.body.innerHTML = DOMPurify.sanitize(m.body);
+ w.focus();
+ }}/>;
+ }
+ break;
+ default:
+ return null;
+ }
+ };
+
+ 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"));
+ }
+ };
+
+ closeRenderWindow = () => {
+ if (this.state.renderWindow) {
+ this.state.renderWindow.close();
+ }
+ };
+
+ render() {
+ const conn = this.props.connection || {
+ "ip_src": "0.0.0.0",
+ "ip_dst": "0.0.0.0",
+ "port_src": "0",
+ "port_dst": "0",
+ "started_at": new Date().toISOString(),
+ };
+ const content = this.state.messages || [];
+
+ let payload = content.map((c, i) =>
+
+
+
+
+ offset: {c.index} | timestamp: {c.timestamp}
+ | retransmitted: {c["is_retransmitted"] ? "yes" : "no"}
+
+ {this.connectionsActions(c)}
+
+
+ {c["from_client"] ? "client" : "server"}
+
+ {this.state.tryParse && this.state.format === "default" ? this.tryParseConnectionMessage(c) : c.content}
+
+
+ );
+
+ return (
+
+
+
+
+ flow: {conn["ip_src"]}:{conn["port_src"]} -> {conn["ip_dst"]}:{conn["port_dst"]}
+ | timestamp: {conn["started_at"]}
+
+
+
+
+
+
+
+
+
+
+
+ {payload}
+ {this.state.messageActionDialog}
+
+ );
+ }
+
+}
+
+
+export default StreamsPane;
diff --git a/frontend/src/components/panels/StreamsPane.scss b/frontend/src/components/panels/StreamsPane.scss
new file mode 100644
index 0000000..d5510cf
--- /dev/null
+++ b/frontend/src/components/panels/StreamsPane.scss
@@ -0,0 +1,113 @@
+@import "../../colors";
+
+.connection-content {
+ height: 100%;
+ background-color: $color-primary-0;
+
+ pre {
+ overflow-x: hidden;
+ height: calc(100% - 31px);
+ padding: 0 10px;
+ white-space: pre-wrap;
+ word-break: break-word;
+
+ p {
+ margin: 0;
+ padding: 0;
+ }
+ }
+
+ .connection-message {
+ position: relative;
+ margin: 10px 0;
+ border: 4px solid $color-primary-3;
+ border-top: 0;
+
+ .connection-message-header {
+ height: 25px;
+ background-color: $color-primary-3;
+
+ .connection-message-info {
+ font-size: 11px;
+ margin-top: 6px;
+ margin-left: -10px;
+ }
+
+ .connection-message-actions {
+ display: none;
+ margin-right: -18px;
+
+ button {
+ font-size: 11px;
+ margin: 0 3px;
+ padding: 5px;
+ }
+ }
+ }
+
+ .message-content {
+ padding: 10px;
+
+ .react-json-view {
+ background-color: inherit !important;
+ }
+ }
+
+ &:hover .connection-message-actions {
+ display: flex;
+ }
+
+ .connection-message-label {
+ font-size: 12px;
+ position: absolute;
+ top: 0;
+ padding: 10px 0;
+ background-color: $color-primary-3;
+ writing-mode: vertical-rl;
+ text-orientation: mixed;
+ }
+
+ &.from-client {
+ margin-right: 100px;
+ color: $color-primary-4;
+
+ .connection-message-label {
+ right: -22px;
+ }
+ }
+
+ &.from-server {
+ margin-left: 100px;
+ color: $color-primary-4;
+
+ .connection-message-label {
+ left: -22px;
+ transform: rotate(-180deg);
+ }
+ }
+ }
+
+ .connection-content-header {
+ height: 33px;
+ padding: 0;
+ background-color: $color-primary-3;
+
+ .header-info {
+ font-size: 12px;
+ padding-top: 7px;
+ padding-left: 25px;
+ }
+
+ .header-actions {
+ display: flex;
+
+ .choice-field {
+ margin-top: -5px;
+
+ .field-value {
+ background-color: $color-primary-3;
+ }
+ }
+ }
+ }
+}
diff --git a/frontend/src/index.js b/frontend/src/index.js
index e3e48de..ca1273a 100644
--- a/frontend/src/index.js
+++ b/frontend/src/index.js
@@ -19,7 +19,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';
import './index.scss';
-import App from './views/App';
+import App from './components/App';
import * as serviceWorker from './serviceWorker';
import notifications from "./notifications";
diff --git a/frontend/src/views/App.js b/frontend/src/views/App.js
deleted file mode 100644
index 8105117..0000000
--- a/frontend/src/views/App.js
+++ /dev/null
@@ -1,81 +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 './App.scss';
-import Header from "./Header";
-import MainPane from "../components/panels/MainPane";
-import Timeline from "./Timeline";
-import {BrowserRouter as Router} from "react-router-dom";
-import Filters from "./Filters";
-import ConfigurationPane from "../components/panels/ConfigurationPane";
-import Notifications from "../components/Notifications";
-import dispatcher from "../dispatcher";
-
-class App extends Component {
-
- state = {};
-
- componentDidMount() {
- dispatcher.register("notifications", payload => {
- if (payload.event === "connected") {
- this.setState({
- connected: true,
- configured: payload.message["is_configured"]
- });
- }
- });
-
- setInterval(() => {
- if (document.title.endsWith("❚")) {
- document.title = document.title.slice(0, -1);
- } else {
- document.title += "❚";
- }
- }, 500);
- }
-
- render() {
- let modal;
- if (this.state.filterWindowOpen && this.state.configured) {
- modal = this.setState({filterWindowOpen: false})}/>;
- }
-
- return (
-
-
- {this.state.connected &&
-
-
- this.setState({filterWindowOpen: true})}/>
-
-
- {this.state.configured ? :
- this.setState({configured: true})}/>}
- {modal}
-
-
- {this.state.configured && }
-
-
- }
-
- );
- }
-}
-
-export default App;
diff --git a/frontend/src/views/App.scss b/frontend/src/views/App.scss
deleted file mode 100644
index 87661c3..0000000
--- a/frontend/src/views/App.scss
+++ /dev/null
@@ -1,16 +0,0 @@
-
-.main {
- display: flex;
- flex-direction: column;
- height: 100vh;
-
- .main-content {
- overflow: hidden;
- flex: 1 1;
- }
-
- .main-header,
- .main-footer {
- flex: 0 0;
- }
-}
diff --git a/frontend/src/views/Connections.js b/frontend/src/views/Connections.js
deleted file mode 100644
index b2edd3f..0000000
--- a/frontend/src/views/Connections.js
+++ /dev/null
@@ -1,293 +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 './Connections.scss';
-import Connection from "../components/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 "../components/ConnectionMatchedRules";
-import log from "../log";
-import ButtonField from "../components/fields/ButtonField";
-import dispatcher from "../dispatcher";
-
-class Connections extends Component {
-
- state = {
- loading: false,
- connections: [],
- firstConnection: null,
- lastConnection: null,
- };
-
- constructor(props) {
- super(props);
-
- this.scrollTopThreashold = 0.00001;
- this.scrollBottomThreashold = 0.99999;
- this.maxConnections = 200;
- this.queryLimit = 50;
- this.connectionsListRef = React.createRef();
- this.lastScrollPosition = 0;
- this.doQueryStringRedirect = false;
- this.doSelectedConnectionRedirect = false;
- }
-
- componentDidMount() {
- this.loadConnections({limit: this.queryLimit})
- .then(() => this.setState({loaded: true}));
- if (this.props.initialConnection) {
- this.setState({selected: this.props.initialConnection.id});
- }
-
- dispatcher.register("timeline_updates", 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("notifications", 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"));
- }
- });
- }
-
- connectionSelected = (c) => {
- this.doSelectedConnectionRedirect = true;
- this.setState({selected: c.id});
- this.props.onSelected(c);
- };
-
- componentDidUpdate(prevProps, prevState, snapshot) {
- if (this.state.loaded && prevProps.location.search !== this.props.location.search) {
- this.loadConnections({limit: this.queryLimit})
- .then(() => log.info("Connections reloaded after query string update"));
- }
- }
-
- handleScroll = (e) => {
- if (this.disableScrollHandler) {
- this.lastScrollPosition = e.currentTarget.scrollTop;
- return;
- }
-
- let relativeScroll = e.currentTarget.scrollTop / (e.currentTarget.scrollHeight - e.currentTarget.clientHeight);
- if (!this.state.loading && relativeScroll > this.scrollBottomThreashold) {
- this.loadConnections({from: this.state.lastConnection.id, limit: this.queryLimit,})
- .then(() => log.info("Following connections loaded"));
- }
- if (!this.state.loading && relativeScroll < this.scrollTopThreashold) {
- this.loadConnections({to: this.state.firstConnection.id, limit: this.queryLimit,})
- .then(() => log.info("Previous connections loaded"));
- if (this.state.showMoreRecentButton) {
- this.setState({showMoreRecentButton: false});
- }
- } else {
- if (this.lastScrollPosition > e.currentTarget.scrollTop) {
- if (!this.state.showMoreRecentButton) {
- this.setState({showMoreRecentButton: true});
- }
- } else {
- if (this.state.showMoreRecentButton) {
- this.setState({showMoreRecentButton: false});
- }
- }
- }
- 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(params) {
- let url = "/api/connections";
- const urlParams = new URLSearchParams(this.props.location.search);
- for (const [name, value] of Object.entries(params)) {
- urlParams.set(name, value);
- }
-
- this.setState({loading: true});
- if (!this.state.rules) {
- await this.loadRules();
- }
- if (!this.state.services) {
- await this.loadServices();
- }
-
- let res = (await backend.get(`${url}?${urlParams}`)).json;
-
- let connections = this.state.connections;
- let firstConnection = this.state.firstConnection;
- let lastConnection = this.state.lastConnection;
-
- if (params !== undefined && params.from !== undefined && params.to === undefined) {
- if (res.length > 0) {
- connections = this.state.connections.concat(res.slice(1));
- lastConnection = connections[connections.length - 1];
- if (connections.length > this.maxConnections) {
- connections = connections.slice(connections.length - this.maxConnections,
- connections.length - 1);
- firstConnection = connections[0];
- }
- }
- } else if (params !== undefined && params.to !== undefined && params.from === undefined) {
- if (res.length > 0) {
- connections = res.slice(0, res.length - 1).concat(this.state.connections);
- firstConnection = connections[0];
- if (connections.length > this.maxConnections) {
- connections = connections.slice(0, this.maxConnections);
- lastConnection = connections[this.maxConnections - 1];
- }
- }
- } else {
- if (res.length > 0) {
- connections = res;
- firstConnection = connections[0];
- lastConnection = connections[connections.length - 1];
- } else {
- connections = [];
- firstConnection = null;
- lastConnection = null;
- }
- }
-
- this.setState({
- loading: false,
- connections: connections,
- firstConnection: firstConnection,
- lastConnection: lastConnection
- });
-
- if (firstConnection != null && lastConnection != null) {
- dispatcher.dispatch("connection_updates", {
- from: new Date(lastConnection["started_at"]),
- to: new Date(firstConnection["started_at"])
- });
- }
- }
-
- loadRules = async () => {
- return backend.get("/api/rules").then(res => this.setState({rules: res.json}));
- };
-
- loadServices = async () => {
- return backend.get("/api/services").then(res => this.setState({services: res.json}));
- };
-
- render() {
- let redirect;
- if (this.doSelectedConnectionRedirect) {
- redirect = ;
- this.doSelectedConnectionRedirect = false;
- } else if (this.doQueryStringRedirect) {
- redirect = ;
- this.doQueryStringRedirect = false;
- }
-
- let loading = null;
- if (this.state.loading) {
- loading =
- Loading... |
- ;
- }
-
- return (
-
- {this.state.showMoreRecentButton &&
- {
- this.disableScrollHandler = true;
- this.connectionsListRef.current.scrollTop = 0;
- this.loadConnections({limit: this.queryLimit})
- .then(() => {
- this.disableScrollHandler = false;
- log.info("Most recent connections loaded");
- });
- }}/>
- }
-
-
-
-
-
- service |
- srcip |
- srcport |
- dstip |
- dstport |
- started_at |
- duration |
- up |
- down |
- actions |
-
-
-
- {
- 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}
- addServicePortFilter={this.addServicePortFilter}
- services={this.state.services}/>,
- c.matched_rules.length > 0 &&
-
- ];
- })
- }
- {loading}
-
-
-
- {redirect}
-
-
- );
- }
-
-}
-
-export default withRouter(Connections);
diff --git a/frontend/src/views/Connections.scss b/frontend/src/views/Connections.scss
deleted file mode 100644
index de06096..0000000
--- a/frontend/src/views/Connections.scss
+++ /dev/null
@@ -1,38 +0,0 @@
-@import "../colors.scss";
-
-.connections-container {
- position: relative;
- height: 100%;
- background-color: $color-primary-3;
-
- .connections {
- position: relative;
- overflow-y: scroll;
- height: 100%;
-
- .table {
- margin-bottom: 0;
- }
-
- th {
- font-size: 13.5px;
- position: sticky;
- top: 0;
- padding: 5px;
- border: none;
- background-color: $color-primary-3;
- }
-
- &:hover::-webkit-scrollbar-thumb {
- background: $color-secondary-2;
- }
- }
-
- .most-recent-button {
- position: absolute;
- z-index: 20;
- top: 45px;
- left: calc(50% - 50px);
- background-color: red;
- }
-}
diff --git a/frontend/src/views/Filters.js b/frontend/src/views/Filters.js
deleted file mode 100644
index 3dd8280..0000000
--- a/frontend/src/views/Filters.js
+++ /dev/null
@@ -1,116 +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 {Col, Container, Modal, Row, Table} from "react-bootstrap";
-import {filtersDefinitions, filtersNames} from "../components/filters/FiltersDefinitions";
-import ButtonField from "../components/fields/ButtonField";
-
-class Filters extends Component {
-
- constructor(props) {
- super(props);
- let newState = {};
- filtersNames.forEach(elem => newState[`${elem}_active`] = false);
- this.state = newState;
- }
-
- componentDidMount() {
- let newState = {};
- filtersNames.forEach(elem => newState[`${elem}_active`] = localStorage.getItem(`filters.${elem}`) === "true");
- this.setState(newState);
- }
-
- checkboxChangesHandler(filterName, event) {
- this.setState({[`${filterName}_active`]: event.target.checked});
- localStorage.setItem(`filters.${filterName}`, event.target.checked);
- if (typeof window !== "undefined") {
- window.dispatchEvent(new Event("quick-filters"));
- }
- }
-
- generateRows(filtersNames) {
- return filtersNames.map(name =>
-
- this.checkboxChangesHandler(name, event)}/> |
- {filtersDefinitions[name]} |
-
- );
- }
-
- render() {
- return (
-
-
-
- ~/filters
-
-
-
-
-
-
-
-
-
- show |
- filter |
-
-
-
- {this.generateRows(["service_port", "client_address", "min_duration",
- "min_bytes", "started_after", "closed_after", "marked"])}
-
-
-
-
-
-
-
- show |
- filter |
-
-
-
- {this.generateRows(["matched_rules", "client_port", "max_duration",
- "max_bytes", "started_before", "closed_before", "hidden"])}
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
- }
-}
-
-export default Filters;
diff --git a/frontend/src/views/Header.js b/frontend/src/views/Header.js
deleted file mode 100644
index 2cfe9fb..0000000
--- a/frontend/src/views/Header.js
+++ /dev/null
@@ -1,109 +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 Typed from 'typed.js';
-import './Header.scss';
-import {filtersDefinitions, filtersNames} from "../components/filters/FiltersDefinitions";
-import {Link, withRouter} from "react-router-dom";
-import ButtonField from "../components/fields/ButtonField";
-
-class Header extends Component {
-
- constructor(props) {
- super(props);
- let newState = {};
- filtersNames.forEach(elem => newState[`${elem}_active`] = false);
- this.state = newState;
- this.fetchStateFromLocalStorage = this.fetchStateFromLocalStorage.bind(this);
- }
-
- componentDidMount() {
- const options = {
- strings: ["caronte$ "],
- typeSpeed: 50,
- cursorChar: "❚"
- };
- this.typed = new Typed(this.el, options);
-
- this.fetchStateFromLocalStorage();
-
- if (typeof window !== "undefined") {
- window.addEventListener("quick-filters", this.fetchStateFromLocalStorage);
- }
- }
-
- componentWillUnmount() {
- this.typed.destroy();
-
- if (typeof window !== "undefined") {
- window.removeEventListener("quick-filters", this.fetchStateFromLocalStorage);
- }
- }
-
- fetchStateFromLocalStorage() {
- let newState = {};
- filtersNames.forEach(elem => newState[`${elem}_active`] = localStorage.getItem(`filters.${elem}`) === "true");
- this.setState(newState);
- }
-
- render() {
- let quickFilters = filtersNames.filter(name => this.state[`${name}_active`])
- .map(name => {filtersDefinitions[name]})
- .slice(0, 5);
-
- return (
-
- );
- }
-}
-
-export default withRouter(Header);
diff --git a/frontend/src/views/Header.scss b/frontend/src/views/Header.scss
deleted file mode 100644
index 0711159..0000000
--- a/frontend/src/views/Header.scss
+++ /dev/null
@@ -1,34 +0,0 @@
-@import "../colors.scss";
-
-.header {
- height: 80px;
- padding: 15px 30px;
-
- > .row {
- background-color: $color-primary-0;
- }
-
- .header-title {
- width: 200px;
- margin: 5px 0 5px -5px;
- }
-
- .header-buttons {
- display: flex;
- justify-content: flex-end;
- margin: 7px 0;
-
- .button-field {
- margin-left: 7px;
- }
- }
-
- .filters-bar {
- padding: 3px 0;
-
- .filter {
- display: inline-block;
- margin-right: 10px;
- }
- }
-}
diff --git a/frontend/src/views/Timeline.js b/frontend/src/views/Timeline.js
deleted file mode 100644
index ebe3eb9..0000000
--- a/frontend/src/views/Timeline.js
+++ /dev/null
@@ -1,232 +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 './Timeline.scss';
-import {
- ChartContainer,
- ChartRow,
- Charts,
- LineChart,
- MultiBrush,
- Resizable,
- styler,
- YAxis
-} from "react-timeseries-charts";
-import {TimeRange, TimeSeries} from "pondjs";
-import backend from "../backend";
-import ChoiceField from "../components/fields/ChoiceField";
-import {withRouter} from "react-router-dom";
-import log from "../log";
-import dispatcher from "../dispatcher";
-
-const minutes = 60 * 1000;
-
-class Timeline extends Component {
-
- state = {
- metric: "connections_per_service"
- };
-
- constructor() {
- super();
-
- this.disableTimeSeriesChanges = false;
- this.selectionTimeout = null;
- }
-
- filteredPort = () => {
- const urlParams = new URLSearchParams(this.props.location.search);
- 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"));
-
- dispatcher.register("connection_updates", payload => {
- this.setState({
- selection: new TimeRange(payload.from, payload.to),
- });
- });
-
- dispatcher.register("notifications", payload => {
- if (payload.event === "services.edit") {
- this.loadServices().then(() => log.debug("Services reloaded after notification update"));
- }
- });
- }
-
- 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"));
- }
- }
-
- loadStatistics = async (metric, filteredPort) => {
- 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;
- }
-
- const ports = Object.keys(services);
- ports.forEach(s => urlParams.append("ports", s));
-
- const metrics = (await backend.get("/api/statistics?" + urlParams)).json;
- const zeroFilledMetrics = [];
- const toTime = m => new Date(m["range_start"]).getTime();
-
- if (metrics.length > 0) {
- let i = 0;
- for (let interval = toTime(metrics[0]); interval <= toTime(metrics[metrics.length - 1]); interval += minutes) {
- if (interval === toTime(metrics[i])) {
- const m = metrics[i++];
- m["range_start"] = new Date(m["range_start"]);
- zeroFilledMetrics.push(m);
- } else {
- const m = {};
- m["range_start"] = new Date(interval);
- m[metric] = {};
- ports.forEach(p => m[metric][p] = 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)))
- });
- 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),
- start,
- end
- });
- log.debug(`Loaded statistics for metric "${metric}" for services [${ports}]`);
- };
-
- loadServices = async () => {
- const services = (await backend.get("/api/services")).json;
- this.setState({services});
- return services;
- };
-
- createStyler = () => {
- return styler(Object.keys(this.state.services).map(port => {
- return {key: port, color: this.state.services[port].color, width: 2};
- }));
- };
-
- handleTimeRangeChange = (timeRange) => {
- if (!this.disableTimeSeriesChanges) {
- this.setState({timeRange});
- }
- };
-
- handleSelectionChange = (timeRange) => {
- this.disableTimeSeriesChanges = true;
-
- this.setState({selection: timeRange});
- if (this.selectionTimeout) {
- clearTimeout(this.selectionTimeout);
- }
- this.selectionTimeout = setTimeout(() => {
- dispatcher.dispatch("timeline_updates", {
- from: timeRange.begin(),
- to: timeRange.end()
- });
- this.selectionTimeout = null;
- this.disableTimeSeriesChanges = false;
- }, 1000);
- };
-
- aggregateSeries = (func) => {
- const values = this.state.series.columns().map(c => this.state.series[func](c));
- return Math[func](...values);
- };
-
- render() {
- if (!this.state.series) {
- return null;
- }
-
- return (
-
- );
- }
-}
-
-export default withRouter(Timeline);
diff --git a/frontend/src/views/Timeline.scss b/frontend/src/views/Timeline.scss
deleted file mode 100644
index 14360d4..0000000
--- a/frontend/src/views/Timeline.scss
+++ /dev/null
@@ -1,22 +0,0 @@
-@import "../colors.scss";
-
-.footer {
- padding: 15px;
-
- .time-line {
- position: relative;
- background-color: $color-primary-0;
-
- .metric-selection {
- font-size: 0.8em;
- position: absolute;
- top: 5px;
- right: 10px;
- }
- }
-
- svg text {
- font-family: "Fira Code", monospace !important;
- fill: $color-primary-4 !important;
- }
-}
--
cgit v1.2.3-70-g09d2
|