From d5f94b76986615b255b77b2a7b7ed336e5ad4838 Mon Sep 17 00:00:00 2001
From: Emiliano Ciavatta
Date: Wed, 7 Oct 2020 14:58:48 +0200
Subject: Implement notifications
---
frontend/src/components/Notifications.scss | 48 ++++++++++++++++++++++++++++++
1 file changed, 48 insertions(+)
create mode 100644 frontend/src/components/Notifications.scss
(limited to 'frontend/src/components/Notifications.scss')
diff --git a/frontend/src/components/Notifications.scss b/frontend/src/components/Notifications.scss
new file mode 100644
index 0000000..b0c334b
--- /dev/null
+++ b/frontend/src/components/Notifications.scss
@@ -0,0 +1,48 @@
+@import "../colors.scss";
+
+.notifications {
+ position: absolute;
+
+ left: 30px;
+ bottom: 50px;
+ z-index: 50;
+
+ .notifications-list {
+
+ }
+
+ .notification {
+ background-color: $color-green;
+ border-left: 5px solid $color-green-dark;
+ padding: 10px;
+ margin: 10px 0;
+ width: 250px;
+ color: $color-green-light;
+ transform: translateX(-300px);
+ transition: all 1s ease;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+
+ .notification-title {
+ font-size: 0.9em;
+ margin: 0;
+ }
+
+ .notification-description {
+ font-size: 0.8em;
+ }
+
+ &.notification-open {
+ transform: translateX(0px);
+ }
+
+ &.notification-closed {
+ transform: translateY(-50px);
+ opacity: 0;
+ }
+
+ }
+
+
+}
\ No newline at end of file
--
cgit v1.2.3-70-g09d2
From 659833be506e86de277d23f4b48ecce422cfaa5d Mon Sep 17 00:00:00 2001
From: Emiliano Ciavatta
Date: Wed, 7 Oct 2020 15:48:27 +0200
Subject: Fix style issues
---
README.md | 42 ++++++++++++-------------
frontend/src/components/Notifications.scss | 28 ++++++-----------
frontend/src/components/fields/ChoiceField.scss | 4 +--
frontend/src/components/fields/InputField.scss | 15 +++++----
frontend/src/components/fields/common.scss | 3 +-
frontend/src/index.scss | 2 ++
frontend/src/views/App.scss | 3 +-
7 files changed, 48 insertions(+), 49 deletions(-)
(limited to 'frontend/src/components/Notifications.scss')
diff --git a/README.md b/README.md
index 0251be1..75158e2 100644
--- a/README.md
+++ b/README.md
@@ -13,23 +13,23 @@ The patterns can be defined as regex or using protocol specific rules.
The connection flows are saved into a database and can be visualized with the web application. REST API are also provided.
## Features
-- immediate installation with docker-compose
-- no configuration file, settings can be changed via GUI or API
-- the pcaps to be analyzed can be loaded via `curl`, either locally or remotely, or via the GUI
- - it is also possible to download the pcaps from the GUI and see all the analysis statistics for each pcap
-- rules can be created to identify connections that contain certain strings
- - pattern matching is done through regular expressions (regex)
- - regex in UTF-8 and Unicode format are also supported
- - it is possible to add an additional filter to the connections identified through pattern matching by type of connection
-- the connections can be labeled by type of service, identified by the port number
- - each service can be assigned a different color
-- it is possible to filter connections by addresses, ports, dimensions, time, duration, matched rules
-- supports both IPv4 and IPv6 addresses
- - if more addresses are assigned to the vulnerable machine to be defended, a CIDR address can be used
-- the detected HTTP connections are automatically reconstructed
- - HTTP requests can be replicated through `curl`, `fetch` and `python requests`
- - compressed HTTP responses (gzip/deflate) are automatically decompressed
-- it is possible to export and view the content of connections in various formats, including hex and base64
+- immediate installation with docker-compose
+- no configuration file, settings can be changed via GUI or API
+- the pcaps to be analyzed can be loaded via `curl`, either locally or remotely, or via the GUI
+ - it is also possible to download the pcaps from the GUI and see all the analysis statistics for each pcap
+- rules can be created to identify connections that contain certain strings
+ - pattern matching is done through regular expressions (regex)
+ - regex in UTF-8 and Unicode format are also supported
+ - it is possible to add an additional filter to the connections identified through pattern matching by type of connection
+- the connections can be labeled by type of service, identified by the port number
+ - each service can be assigned a different color
+- it is possible to filter connections by addresses, ports, dimensions, time, duration, matched rules
+- supports both IPv4 and IPv6 addresses
+ - if more addresses are assigned to the vulnerable machine to be defended, a CIDR address can be used
+- the detected HTTP connections are automatically reconstructed
+ - HTTP requests can be replicated through `curl`, `fetch` and `python requests`
+ - compressed HTTP responses (gzip/deflate) are automatically decompressed
+- it is possible to export and view the content of connections in various formats, including hex and base64
## Installation
There are two ways to install Caronte:
@@ -77,16 +77,16 @@ The backend, written in Go language, it is designed as a service. It exposes RES
## Screenshots
Below there are some screenshots showing the main features of the tool.
-#### Viewing the contents of a connection
+### Viewing the contents of a connection
![Connection Content](frontend/screenshots/connection_content.png)
-#### Loading pcaps and analysis details
+### Loading pcaps and analysis details
![Connection Content](frontend/screenshots/pcaps.png)
-#### Creating new pattern matching rules
+### Creating new pattern matching rules
![Connection Content](frontend/screenshots/rules.png)
-#### Creating or editing services
+### Creating or editing services
![Connection Content](frontend/screenshots/services.png)
## License
diff --git a/frontend/src/components/Notifications.scss b/frontend/src/components/Notifications.scss
index b0c334b..bec7734 100644
--- a/frontend/src/components/Notifications.scss
+++ b/frontend/src/components/Notifications.scss
@@ -2,27 +2,22 @@
.notifications {
position: absolute;
-
- left: 30px;
- bottom: 50px;
z-index: 50;
-
- .notifications-list {
-
- }
+ bottom: 50px;
+ left: 30px;
.notification {
- background-color: $color-green;
- border-left: 5px solid $color-green-dark;
- padding: 10px;
- margin: 10px 0;
+ overflow: hidden;
width: 250px;
- color: $color-green-light;
- transform: translateX(-300px);
+ margin: 10px 0;
+ padding: 10px;
transition: all 1s ease;
+ transform: translateX(-300px);
white-space: nowrap;
- overflow: hidden;
text-overflow: ellipsis;
+ color: $color-green-light;
+ border-left: 5px solid $color-green-dark;
+ background-color: $color-green;
.notification-title {
font-size: 0.9em;
@@ -41,8 +36,5 @@
transform: translateY(-50px);
opacity: 0;
}
-
}
-
-
-}
\ No newline at end of file
+}
diff --git a/frontend/src/components/fields/ChoiceField.scss b/frontend/src/components/fields/ChoiceField.scss
index 0b5e510..c8c7ff1 100644
--- a/frontend/src/components/fields/ChoiceField.scss
+++ b/frontend/src/components/fields/ChoiceField.scss
@@ -19,7 +19,7 @@
border-radius: 5px;
background-color: $color-primary-2;
- &:after {
+ &::after {
position: absolute;
right: 10px;
content: "⋎";
@@ -58,7 +58,7 @@
display: block;
}
- .field-value:after {
+ .field-value::after {
content: "⋏";
}
}
diff --git a/frontend/src/components/fields/InputField.scss b/frontend/src/components/fields/InputField.scss
index 7cc34d9..e8ef46a 100644
--- a/frontend/src/components/fields/InputField.scss
+++ b/frontend/src/components/fields/InputField.scss
@@ -28,7 +28,7 @@
display: none;
}
- .file-label:after {
+ .file-label::after {
position: absolute;
top: 0;
right: 0;
@@ -47,12 +47,13 @@
background-color: $color-primary-4 !important;
}
- .field-value input, .field-value .file-label {
+ .field-value input,
+ .field-value .file-label {
color: $color-primary-3 !important;
background-color: $color-primary-4 !important;
}
- .file-label:after {
+ .file-label::after {
background-color: $color-secondary-4 !important;
}
}
@@ -63,12 +64,13 @@
background-color: $color-secondary-2 !important;
}
- .field-value input, .field-value .file-label {
+ .field-value input,
+ .field-value .file-label {
color: $color-primary-4 !important;
background-color: $color-secondary-2 !important;
}
- .file-label:after {
+ .file-label::after {
background-color: $color-secondary-1 !important;
}
}
@@ -90,7 +92,8 @@
.field-input {
width: 100%;
- input, .file-label {
+ input,
+ .file-label {
padding-left: 3px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
diff --git a/frontend/src/components/fields/common.scss b/frontend/src/components/fields/common.scss
index f37369e..8fbef0d 100644
--- a/frontend/src/components/fields/common.scss
+++ b/frontend/src/components/fields/common.scss
@@ -1,7 +1,8 @@
@import "../../colors.scss";
.field {
- input, textarea {
+ input,
+ textarea {
width: 100%;
padding: 7px 10px;
color: $color-primary-4;
diff --git a/frontend/src/index.scss b/frontend/src/index.scss
index 2e5b6b9..9d6afc4 100644
--- a/frontend/src/index.scss
+++ b/frontend/src/index.scss
@@ -1,7 +1,9 @@
@import url("https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500&display=swap");
@import-normalize ;
+
@import "colors.scss";
+
body {
font-family: "Fira Code", monospace;
font-size: 100%;
diff --git a/frontend/src/views/App.scss b/frontend/src/views/App.scss
index 5c5bd99..87661c3 100644
--- a/frontend/src/views/App.scss
+++ b/frontend/src/views/App.scss
@@ -9,7 +9,8 @@
flex: 1 1;
}
- .main-header, .main-footer {
+ .main-header,
+ .main-footer {
flex: 0 0;
}
}
--
cgit v1.2.3-70-g09d2
From 2b2b8e66e7244592672c283fe7bb5d9a1fd9da99 Mon Sep 17 00:00:00 2001
From: Emiliano Ciavatta
Date: Thu, 8 Oct 2020 11:16:38 +0200
Subject: Minor changes
---
application_router.go | 6 +-
connections_controller.go | 4 +-
frontend/src/components/Connection.js | 33 ++---
frontend/src/components/Connection.scss | 4 +
frontend/src/components/Notifications.js | 14 +-
frontend/src/components/Notifications.scss | 1 +
frontend/src/views/App.js | 4 +-
frontend/src/views/Connections.js | 44 ++++--
frontend/src/views/Footer.js | 176 -----------------------
frontend/src/views/Footer.scss | 22 ---
frontend/src/views/Header.js | 24 ++--
frontend/src/views/Timeline.js | 215 +++++++++++++++++++++++++++++
frontend/src/views/Timeline.scss | 22 +++
13 files changed, 313 insertions(+), 256 deletions(-)
delete mode 100644 frontend/src/views/Footer.js
delete mode 100644 frontend/src/views/Footer.scss
create mode 100644 frontend/src/views/Timeline.js
create mode 100644 frontend/src/views/Timeline.scss
(limited to 'frontend/src/components/Notifications.scss')
diff --git a/application_router.go b/application_router.go
index 6431e22..30ec7c6 100644
--- a/application_router.go
+++ b/application_router.go
@@ -269,9 +269,9 @@ func CreateApplicationRouter(applicationContext *ApplicationContext,
}
if result {
- c.Status(http.StatusAccepted)
- notificationController.Notify("connections.action", UpdateNotification,
- gin.H{"connection_id": c.Param("id"), "action": c.Param("action")})
+ response := gin.H{"connection_id": c.Param("id"), "action": c.Param("action")}
+ success(c, response)
+ notificationController.Notify("connections.action", UpdateNotification, response)
} else {
notFound(c, gin.H{"connection": id})
}
diff --git a/connections_controller.go b/connections_controller.go
index 2894193..e872c9f 100644
--- a/connections_controller.go
+++ b/connections_controller.go
@@ -68,11 +68,11 @@ func (cc ConnectionsController) GetConnections(c context.Context, filter Connect
from, _ := RowIDFromHex(filter.From)
if !from.IsZero() {
- query = query.Filter(OrderedDocument{{"_id", UnorderedDocument{"$lt": from}}})
+ query = query.Filter(OrderedDocument{{"_id", UnorderedDocument{"$lte": from}}})
}
to, _ := RowIDFromHex(filter.To)
if !to.IsZero() {
- query = query.Filter(OrderedDocument{{"_id", UnorderedDocument{"$gt": to}}})
+ query = query.Filter(OrderedDocument{{"_id", UnorderedDocument{"$gte": to}}})
} else {
query = query.Sort("_id", false)
}
diff --git a/frontend/src/components/Connection.js b/frontend/src/components/Connection.js
index 46a0cab..b7e2531 100644
--- a/frontend/src/components/Connection.js
+++ b/frontend/src/components/Connection.js
@@ -4,6 +4,7 @@ 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');
@@ -54,9 +55,9 @@ class Connection extends Component {
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 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()}
@@ -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 c21541a31fe45ba3a0bafca46415247f3837713e Mon Sep 17 00:00:00 2001
From: Emiliano Ciavatta
Date: Fri, 9 Oct 2020 17:07:24 +0200
Subject: Add MainPane
---
Dockerfile | 5 +-
VERSION | 1 -
application_router.go | 16 ++--
caronte.go | 11 ++-
frontend/public/favicon.ico | Bin 34239 -> 12163 bytes
frontend/public/logo192.png | Bin 34239 -> 6498 bytes
frontend/public/logo512.png | Bin 34239 -> 26806 bytes
frontend/src/components/App.js | 5 +-
frontend/src/components/Notifications.js | 99 ++++++++++++++-------
frontend/src/components/Notifications.scss | 16 +++-
frontend/src/components/Timeline.js | 8 +-
frontend/src/components/Timeline.scss | 4 +
frontend/src/components/dialogs/Filters.js | 7 +-
frontend/src/components/fields/ButtonField.js | 4 +-
frontend/src/components/fields/TextField.scss | 4 +
.../src/components/filters/FiltersDefinitions.js | 38 ++------
frontend/src/components/objects/Connection.js | 39 +++-----
frontend/src/components/objects/Connection.scss | 4 +
frontend/src/components/objects/LinkPopover.scss | 5 ++
frontend/src/components/pages/MainPage.js | 2 +-
frontend/src/components/pages/MainPage.scss | 1 +
frontend/src/components/panels/ConnectionsPane.js | 12 ++-
.../src/components/panels/ConnectionsPane.scss | 5 +-
frontend/src/components/panels/MainPane.js | 82 ++++++++++++++++-
frontend/src/components/panels/MainPane.scss | 27 +++++-
frontend/src/components/panels/StreamsPane.js | 5 +-
frontend/src/components/panels/StreamsPane.scss | 9 +-
frontend/src/components/panels/common.scss | 8 ++
frontend/src/index.scss | 12 +++
frontend/src/logo.svg | 8 +-
notification_controller.go | 8 +-
resources_controller.go | 2 +-
32 files changed, 297 insertions(+), 150 deletions(-)
delete mode 100644 VERSION
(limited to 'frontend/src/components/Notifications.scss')
diff --git a/Dockerfile b/Dockerfile
index cf7730b..a9c8134 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,15 +3,16 @@ FROM ubuntu:20.04 AS BUILDSTAGE
# Install tools and libraries
RUN apt-get update && \
- DEBIAN_FRONTEND=noninteractive apt-get install -qq golang-1.14 pkg-config libpcap-dev libhyperscan-dev yarnpkg
+ DEBIAN_FRONTEND=noninteractive apt-get install -qq git golang-1.14 pkg-config libpcap-dev libhyperscan-dev yarnpkg
COPY . /caronte
WORKDIR /caronte
RUN ln -sf ../lib/go-1.14/bin/go /usr/bin/go && \
+ export VERSION=$(git describe --tags) && \
go mod download && \
- go build && \
+ go build -ldflags "-X main.Version=$VERSION" && \
cd frontend && \
yarnpkg install && \
yarnpkg build --production=true && \
diff --git a/VERSION b/VERSION
deleted file mode 100644
index bc1f22f..0000000
--- a/VERSION
+++ /dev/null
@@ -1 +0,0 @@
-v0.20.10
\ No newline at end of file
diff --git a/application_router.go b/application_router.go
index 9fd7e3d..89b471b 100644
--- a/application_router.go
+++ b/application_router.go
@@ -65,7 +65,7 @@ func CreateApplicationRouter(applicationContext *ApplicationContext,
applicationContext.SetAccounts(settings.Accounts)
c.JSON(http.StatusAccepted, gin.H{})
- notificationController.Notify("setup", InsertNotification, gin.H{})
+ notificationController.Notify("setup", gin.H{})
})
router.GET("/ws", func(c *gin.Context) {
@@ -95,7 +95,7 @@ func CreateApplicationRouter(applicationContext *ApplicationContext,
} else {
response := UnorderedDocument{"id": id}
success(c, response)
- notificationController.Notify("rules.new", InsertNotification, response)
+ notificationController.Notify("rules.new", response)
}
})
@@ -134,7 +134,7 @@ func CreateApplicationRouter(applicationContext *ApplicationContext,
notFound(c, UnorderedDocument{"id": id})
} else {
success(c, rule)
- notificationController.Notify("rules.edit", UpdateNotification, rule)
+ notificationController.Notify("rules.edit", rule)
}
})
@@ -156,7 +156,7 @@ func CreateApplicationRouter(applicationContext *ApplicationContext,
} else {
response := gin.H{"session": sessionID}
c.JSON(http.StatusAccepted, response)
- notificationController.Notify("pcap.upload", InsertNotification, response)
+ notificationController.Notify("pcap.upload", response)
}
})
@@ -190,7 +190,7 @@ func CreateApplicationRouter(applicationContext *ApplicationContext,
} else {
response := gin.H{"session": sessionID}
c.JSON(http.StatusAccepted, response)
- notificationController.Notify("pcap.file", InsertNotification, response)
+ notificationController.Notify("pcap.file", response)
}
})
@@ -227,7 +227,7 @@ func CreateApplicationRouter(applicationContext *ApplicationContext,
session := gin.H{"session": sessionID}
if cancelled := applicationContext.PcapImporter.CancelSession(sessionID); cancelled {
c.JSON(http.StatusAccepted, session)
- notificationController.Notify("sessions.delete", DeleteNotification, session)
+ notificationController.Notify("sessions.delete", session)
} else {
notFound(c, session)
}
@@ -288,7 +288,7 @@ func CreateApplicationRouter(applicationContext *ApplicationContext,
if result {
response := gin.H{"connection_id": c.Param("id"), "action": c.Param("action")}
success(c, response)
- notificationController.Notify("connections.action", UpdateNotification, response)
+ notificationController.Notify("connections.action", response)
} else {
notFound(c, gin.H{"connection": id})
}
@@ -344,7 +344,7 @@ func CreateApplicationRouter(applicationContext *ApplicationContext,
}
if err := applicationContext.ServicesController.SetService(c, service); err == nil {
success(c, service)
- notificationController.Notify("services.edit", UpdateNotification, service)
+ notificationController.Notify("services.edit", service)
} else {
unprocessableEntity(c, err)
}
diff --git a/caronte.go b/caronte.go
index d4265bc..2d24af6 100644
--- a/caronte.go
+++ b/caronte.go
@@ -21,9 +21,10 @@ import (
"flag"
"fmt"
log "github.com/sirupsen/logrus"
- "io/ioutil"
)
+var Version string
+
func main() {
mongoHost := flag.String("mongo-host", "localhost", "address of MongoDB")
mongoPort := flag.Int("mongo-port", 27017, "port of MongoDB")
@@ -40,12 +41,10 @@ func main() {
log.WithError(err).WithFields(logFields).Fatal("failed to connect to MongoDB")
}
- versionBytes, err := ioutil.ReadFile("VERSION")
- if err != nil {
- log.WithError(err).Fatal("failed to load version file")
+ if Version == "" {
+ Version = "undefined"
}
-
- applicationContext, err := CreateApplicationContext(storage, string(versionBytes))
+ applicationContext, err := CreateApplicationContext(storage, Version)
if err != nil {
log.WithError(err).WithFields(logFields).Fatal("failed to create application context")
}
diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico
index 1dc499d..be9cec8 100644
Binary files a/frontend/public/favicon.ico and b/frontend/public/favicon.ico differ
diff --git a/frontend/public/logo192.png b/frontend/public/logo192.png
index 1dc499d..1969e1d 100644
Binary files a/frontend/public/logo192.png and b/frontend/public/logo192.png differ
diff --git a/frontend/public/logo512.png b/frontend/public/logo512.png
index 1dc499d..3afb127 100644
Binary files a/frontend/public/logo512.png and b/frontend/public/logo512.png differ
diff --git a/frontend/src/components/App.js b/frontend/src/components/App.js
index bf959c5..0f700db 100644
--- a/frontend/src/components/App.js
+++ b/frontend/src/components/App.js
@@ -31,7 +31,8 @@ class App extends Component {
if (payload.event === "connected") {
this.setState({
connected: true,
- configured: payload.message["is_configured"]
+ configured: payload.message["is_configured"],
+ version: payload.message["version"]
});
}
});
@@ -50,7 +51,7 @@ class App extends Component {
<>
{this.state.connected ?
- (this.state.configured ?
:
+ (this.state.configured ?
:
this.setState({configured: true})}/>) :
}
diff --git a/frontend/src/components/Notifications.js b/frontend/src/components/Notifications.js
index 1017a42..ad681a2 100644
--- a/frontend/src/components/Notifications.js
+++ b/frontend/src/components/Notifications.js
@@ -30,49 +30,84 @@ class Notifications extends Component {
};
componentDidMount() {
- dispatcher.register("notifications", notification => {
+ dispatcher.register("notifications", n => this.notificationHandler(n));
+ }
+
+ notificationHandler = (n) => {
+ switch (n.event) {
+ case "connected":
+ n.title = "connected";
+ n.description = `number of active clients: ${n.message["connected_clients"]}`;
+ return this.pushNotification(n);
+ case "services.edit":
+ n.title = "services updated";
+ n.description = `updated "${n.message["name"]}" on port ${n.message["port"]}`;
+ n.variant = "blue";
+ return this.pushNotification(n);
+ case "rules.new":
+ n.title = "rules updated";
+ n.description = `new rule added: ${n.message["name"]}`;
+ n.variant = "green";
+ return this.pushNotification(n);
+ case "rules.edit":
+ n.title = "rules updated";
+ n.description = `existing rule updated: ${n.message["name"]}`;
+ n.variant = "blue";
+ return this.pushNotification(n);
+ default:
+ return;
+ }
+ };
+
+ pushNotification = (notification) => {
+ const notifications = this.state.notifications;
+ notifications.push(notification);
+ this.setState({notifications});
+ setTimeout(() => {
const notifications = this.state.notifications;
- notifications.push(notification);
+ notification.open = true;
this.setState({notifications});
- setTimeout(() => {
- const notifications = this.state.notifications;
- notification.open = true;
- this.setState({notifications});
- }, 100);
+ }, 100);
- 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);
+ 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);
- const removeHandle = setTimeout(() => {
- const closedNotifications = _.without(this.state.closedNotifications, notification);
- this.setState({closedNotifications});
- }, 6000);
+ 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});
- };
- });
- }
+ notification.onClick = () => {
+ clearTimeout(hideHandle);
+ clearTimeout(removeHandle);
+ const notifications = _.without(this.state.notifications, notification);
+ this.setState({notifications});
+ };
+ };
render() {
return (
{
- this.state.closedNotifications.concat(this.state.notifications).map(n =>
-
-
{n.event}
- {JSON.stringify(n.message)}
-
- )
+ this.state.closedNotifications.concat(this.state.notifications).map(n => {
+ const notificationClassnames = {
+ "notification": true,
+ "notification-closed": n.closed,
+ "notification-open": n.open
+ };
+ if (n.variant) {
+ notificationClassnames[`notification-${n.variant}`] = true;
+ }
+ return
+
{n.title}
+
{n.description}
+
;
+ })
}
diff --git a/frontend/src/components/Notifications.scss b/frontend/src/components/Notifications.scss
index 324d0bb..98d228e 100644
--- a/frontend/src/components/Notifications.scss
+++ b/frontend/src/components/Notifications.scss
@@ -7,18 +7,15 @@
left: 30px;
.notification {
- overflow: hidden;
width: 250px;
margin: 10px 0;
padding: 10px;
+ cursor: pointer;
transition: all 1s ease;
transform: translateX(-300px);
- white-space: nowrap;
- text-overflow: ellipsis;
color: $color-green-light;
border-left: 5px solid $color-green-dark;
background-color: $color-green;
- cursor: pointer;
.notification-title {
font-size: 0.9em;
@@ -27,6 +24,11 @@
.notification-description {
font-size: 0.8em;
+ overflow: hidden;
+ margin: 10px 0;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ color: $color-primary-4;
}
&.notification-open {
@@ -37,5 +39,11 @@
transform: translateY(-50px);
opacity: 0;
}
+
+ &.notification-blue {
+ color: $color-blue-light;
+ border-left: 5px solid $color-blue-dark;
+ background-color: $color-blue;
+ }
}
}
diff --git a/frontend/src/components/Timeline.js b/frontend/src/components/Timeline.js
index 7be42e0..615203f 100644
--- a/frontend/src/components/Timeline.js
+++ b/frontend/src/components/Timeline.js
@@ -35,6 +35,7 @@ import log from "../log";
import dispatcher from "../dispatcher";
const minutes = 60 * 1000;
+const classNames = require('classnames');
class Timeline extends Component {
@@ -70,6 +71,11 @@ class Timeline extends Component {
this.loadServices().then(() => log.debug("Services reloaded after notification update"));
}
});
+
+ dispatcher.register("pulse_timeline", payload => {
+ this.setState({pulseTimeline: true});
+ setTimeout(() => this.setState({pulseTimeline: false}), payload.duration);
+ });
}
componentDidUpdate(prevProps, prevState, snapshot) {
@@ -183,7 +189,7 @@ class Timeline extends Component {
return (
-
+
{this.generateRows(["service_port", "client_address", "min_duration",
- "min_bytes", "started_after", "closed_after", "marked"])}
+ "min_bytes"])}
@@ -95,14 +95,11 @@ class Filters extends Component {
{this.generateRows(["matched_rules", "client_port", "max_duration",
- "max_bytes", "started_before", "closed_before", "hidden"])}
+ "max_bytes", "marked"])}
-
-
-
diff --git a/frontend/src/components/fields/ButtonField.js b/frontend/src/components/fields/ButtonField.js
index ffcceae..193339c 100644
--- a/frontend/src/components/fields/ButtonField.js
+++ b/frontend/src/components/fields/ButtonField.js
@@ -55,8 +55,8 @@ class ButtonField extends Component {
}
return (
-
-
+ {this.props.name}
);
diff --git a/frontend/src/components/fields/TextField.scss b/frontend/src/components/fields/TextField.scss
index c2d6ef5..5fde9e6 100644
--- a/frontend/src/components/fields/TextField.scss
+++ b/frontend/src/components/fields/TextField.scss
@@ -51,4 +51,8 @@
padding: 5px 10px;
color: $color-secondary-0;
}
+
+ &:hover::-webkit-scrollbar-thumb {
+ background: $color-secondary-2 !important;
+ }
}
diff --git a/frontend/src/components/filters/FiltersDefinitions.js b/frontend/src/components/filters/FiltersDefinitions.js
index cde3cfb..9fb3b18 100644
--- a/frontend/src/components/filters/FiltersDefinitions.js
+++ b/frontend/src/components/filters/FiltersDefinitions.js
@@ -22,8 +22,7 @@ import RulesConnectionsFilter from "./RulesConnectionsFilter";
import BooleanConnectionsFilter from "./BooleanConnectionsFilter";
export const filtersNames = ["service_port", "matched_rules", "client_address", "client_port",
- "min_duration", "max_duration", "min_bytes", "max_bytes", "started_after",
- "started_before", "closed_after", "closed_before", "marked", "hidden"];
+ "min_duration", "max_duration", "min_bytes", "max_bytes", "marked"];
export const filtersDefinitions = {
service_port: ,
- // started_after: ,
- // started_before: ,
- // closed_after: ,
- // closed_before: ,
- marked: ,
- // hidden:
+ contains_string: ,
+ marked:
};
diff --git a/frontend/src/components/objects/Connection.js b/frontend/src/components/objects/Connection.js
index 5e2beba..e0e942a 100644
--- a/frontend/src/components/objects/Connection.js
+++ b/frontend/src/components/objects/Connection.js
@@ -17,11 +17,12 @@
import React, {Component} from 'react';
import './Connection.scss';
-import {Form, OverlayTrigger, Popover} from "react-bootstrap";
+import {Form} from "react-bootstrap";
import backend from "../../backend";
import {dateTimeToTime, durationBetween, formatSize} from "../../utils";
import ButtonField from "../fields/ButtonField";
import LinkPopover from "./LinkPopover";
+import TextField from "../fields/TextField";
const classNames = require('classnames');
@@ -81,14 +82,6 @@ class Connection extends Component {
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 &&
}
@@ -97,7 +90,7 @@ class Connection extends Component {
const copyPopoverContent =
{this.state.copiedMessage ? Copied! :
Click to copy the connection id }
-
+
;
return (
@@ -119,22 +112,16 @@ class Connection extends Component {
{durationBetween(startedAt, closedAt)}
{formatSize(conn["client_bytes"])}
{formatSize(conn["server_bytes"])}
-
- Mark this connection)}>
- this.handleAction("mark")}>!!
-
-
- this.handleAction("comment")}>@
-
-
- this.handleAction("copy")}>#
-
+
+ this.handleAction("mark")}>!!}
+ content={Mark this connection } placement="right"/>
+ this.handleAction("comment")}>@}
+ content={commentPopoverContent} placement="right"/>
+ this.handleAction("copy")}>#}
+ content={copyPopoverContent} placement="right"/>
);
diff --git a/frontend/src/components/objects/Connection.scss b/frontend/src/components/objects/Connection.scss
index 3b9f479..bf66272 100644
--- a/frontend/src/components/objects/Connection.scss
+++ b/frontend/src/components/objects/Connection.scss
@@ -46,6 +46,10 @@
.link-popover {
font-weight: 400;
}
+
+ .connection-actions .link-popover {
+ text-decoration: none;
+ }
}
.connection-popover {
diff --git a/frontend/src/components/objects/LinkPopover.scss b/frontend/src/components/objects/LinkPopover.scss
index 725224c..c81f8bb 100644
--- a/frontend/src/components/objects/LinkPopover.scss
+++ b/frontend/src/components/objects/LinkPopover.scss
@@ -5,3 +5,8 @@
cursor: pointer;
text-decoration: underline;
}
+
+.popover {
+ font-family: "Fira Code", monospace;
+ font-size: 0.75em;
+}
diff --git a/frontend/src/components/pages/MainPage.js b/frontend/src/components/pages/MainPage.js
index 7376091..4632bbd 100644
--- a/frontend/src/components/pages/MainPage.js
+++ b/frontend/src/components/pages/MainPage.js
@@ -57,7 +57,7 @@ class MainPage extends Component {
}/>
}/>
- }/>
+ }/>
diff --git a/frontend/src/components/pages/MainPage.scss b/frontend/src/components/pages/MainPage.scss
index 3b1a689..4ca54c0 100644
--- a/frontend/src/components/pages/MainPage.scss
+++ b/frontend/src/components/pages/MainPage.scss
@@ -13,6 +13,7 @@
}
.details-pane {
+ position: relative;
flex: 1 1;
margin-left: 7.5px;
}
diff --git a/frontend/src/components/panels/ConnectionsPane.js b/frontend/src/components/panels/ConnectionsPane.js
index 038ef8f..1f79ab8 100644
--- a/frontend/src/components/panels/ConnectionsPane.js
+++ b/frontend/src/components/panels/ConnectionsPane.js
@@ -27,6 +27,8 @@ import log from "../../log";
import ButtonField from "../fields/ButtonField";
import dispatcher from "../../dispatcher";
+const classNames = require('classnames');
+
class ConnectionsPane extends Component {
state = {
@@ -81,6 +83,11 @@ class ConnectionsPane extends Component {
this.loadServices().then(() => log.debug("Services reloaded after notification update"));
}
});
+
+ dispatcher.register("pulse_connections_view", payload => {
+ this.setState({pulseConnectionsView: true});
+ setTimeout(() => this.setState({pulseConnectionsView: false}), payload.duration);
+ });
}
connectionSelected = (c, doRedirect = true) => {
@@ -246,7 +253,7 @@ class ConnectionsPane extends Component {
return (
{this.state.showMoreRecentButton &&
- {
+ {
this.disableScrollHandler = true;
this.connectionsListRef.current.scrollTop = 0;
this.loadConnections({limit: this.queryLimit})
@@ -257,7 +264,8 @@ class ConnectionsPane extends Component {
}}/>
}
-
+
diff --git a/frontend/src/components/panels/ConnectionsPane.scss b/frontend/src/components/panels/ConnectionsPane.scss
index 06f5827..59fe372 100644
--- a/frontend/src/components/panels/ConnectionsPane.scss
+++ b/frontend/src/components/panels/ConnectionsPane.scss
@@ -33,6 +33,9 @@
z-index: 20;
top: 45px;
left: calc(50% - 50px);
- background-color: red;
+ }
+
+ .connections-pulse {
+ animation: pulse 2s infinite;
}
}
diff --git a/frontend/src/components/panels/MainPane.js b/frontend/src/components/panels/MainPane.js
index 74c859c..8aa8ad8 100644
--- a/frontend/src/components/panels/MainPane.js
+++ b/frontend/src/components/panels/MainPane.js
@@ -17,17 +17,91 @@
import React, {Component} from 'react';
import './common.scss';
-import './ServicesPane.scss';
+import './MainPane.scss';
+import Typed from "typed.js";
+import dispatcher from "../../dispatcher";
+import RulesPane from "./RulesPane";
+import StreamsPane from "./StreamsPane";
+import PcapsPane from "./PcapsPane";
+import ServicesPane from "./ServicesPane";
class MainPane extends Component {
state = {};
+ componentDidMount() {
+ const nl = "^600\n^400";
+ const options = {
+ strings: [
+ `welcome to caronte!^1000 the current version is ${this.props.version}` + nl +
+ "caronte is a network analyzer,^300 it is able to read pcaps and extract connections", // 0
+ "the left panel lists all connections that have already been closed" + nl +
+ "scrolling up the list will load the most recent connections,^300 downward the oldest ones", // 1
+ "by selecting a connection you can view its content,^300 which will be shown in the right panel" + nl +
+ "you can choose the display format,^300 or decide to download the connection content", // 2
+ "below there is the timeline,^300 which shows the number of connections per minute per service" + nl +
+ "you can use the sliding window to move the time range of the connections to be displayed", // 3
+ "there are also additional metrics,^300 selectable from the drop-down menu", // 4
+ "at the top are the filters,^300 which can be used to select only certain types of connections" + nl +
+ "you can choose which filters to display in the top bar from the filters window", // 5
+ "in the pcaps panel it is possible to analyze new pcaps,^300 or to see the pcaps already analyzed" + nl +
+ "you can load pcaps from your browser,^300 or process pcaps already present on the filesystem", // 6
+ "in the rules panel you can see the rules already created,^300 or create new ones" + nl +
+ "the rules inserted will be used only to label new connections, not those already analyzed" + nl +
+ "a connection is tagged if it meets all the requirements specified by the rule", // 7
+ "in the services panel you can assign new services or edit existing ones" + nl +
+ "each service is associated with a port number,^300 and will be shown in the connection list", // 8
+ "from the configuration panel you can change the settings of the frontend application", // 9
+ "that's all! and have fun!" + nl + "created by @eciavatta" // 10
+ ],
+ typeSpeed: 40,
+ cursorChar: "_",
+ backSpeed: 5,
+ smartBackspace: false,
+ backDelay: 1500,
+ preStringTyped: (arrayPos) => {
+ switch (arrayPos) {
+ case 1:
+ return dispatcher.dispatch("pulse_connections_view", {duration: 12000});
+ case 2:
+ return this.setState({backgroundPane: });
+ case 3:
+ this.setState({backgroundPane: null});
+ return dispatcher.dispatch("pulse_timeline", {duration: 12000});
+ case 6:
+ return this.setState({backgroundPane: });
+ case 7:
+ return this.setState({backgroundPane: });
+ case 8:
+ return this.setState({backgroundPane: });
+ case 10:
+ return this.setState({backgroundPane: null});
+ default:
+ return;
+ }
+ },
+ };
+ this.typed = new Typed(this.el, options);
+ }
+
+ componentWillUnmount() {
+ this.typed.destroy();
+ }
+
render() {
return (
-
-
- MainPane
+
+
+
+
+ {
+ this.el = el;
+ }}/>
+
+
+
+
+ {this.state.backgroundPane}
);
diff --git a/frontend/src/components/panels/MainPane.scss b/frontend/src/components/panels/MainPane.scss
index c8460f2..8f99b3c 100644
--- a/frontend/src/components/panels/MainPane.scss
+++ b/frontend/src/components/panels/MainPane.scss
@@ -1,5 +1,30 @@
@import "../../colors";
-.main-pane {
+.pane-container {
+ background-color: $color-primary-0;
+ .main-pane {
+ position: absolute;
+ z-index: 50;
+ top: 0;
+ left: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 100%;
+ background-color: transparent;
+
+ .tutorial {
+ flex-basis: 100%;
+ padding: 5px 10px;
+ text-align: center;
+ background-color: $color-primary-2;
+ }
+ }
+
+ .background-pane {
+ height: 100%;
+ opacity: 0.4;
+ }
}
diff --git a/frontend/src/components/panels/StreamsPane.js b/frontend/src/components/panels/StreamsPane.js
index c8bd121..bd1964e 100644
--- a/frontend/src/components/panels/StreamsPane.js
+++ b/frontend/src/components/panels/StreamsPane.js
@@ -208,8 +208,8 @@ class StreamsPane extends Component {
);
return (
-
-
+
+
flow : {conn["ip_src"]}:{conn["port_src"]} -> {conn["ip_dst"]}:{conn["port_dst"]}
@@ -235,7 +235,6 @@ class StreamsPane extends Component {
);
}
-
}
diff --git a/frontend/src/components/panels/StreamsPane.scss b/frontend/src/components/panels/StreamsPane.scss
index d5510cf..1f641f3 100644
--- a/frontend/src/components/panels/StreamsPane.scss
+++ b/frontend/src/components/panels/StreamsPane.scss
@@ -1,7 +1,6 @@
@import "../../colors";
-.connection-content {
- height: 100%;
+.stream-pane {
background-color: $color-primary-0;
pre {
@@ -15,6 +14,10 @@
margin: 0;
padding: 0;
}
+
+ &:hover::-webkit-scrollbar-thumb {
+ background: $color-secondary-2;
+ }
}
.connection-message {
@@ -87,7 +90,7 @@
}
}
- .connection-content-header {
+ .stream-pane-header {
height: 33px;
padding: 0;
background-color: $color-primary-3;
diff --git a/frontend/src/components/panels/common.scss b/frontend/src/components/panels/common.scss
index 1468f35..335e65b 100644
--- a/frontend/src/components/panels/common.scss
+++ b/frontend/src/components/panels/common.scss
@@ -32,6 +32,10 @@
margin-left: 10px;
color: $color-secondary-0;
}
+
+ &:hover::-webkit-scrollbar-thumb {
+ background: $color-secondary-2;
+ }
}
table {
@@ -96,4 +100,8 @@
margin-left: 5px;
}
}
+
+ &:hover::-webkit-scrollbar-thumb {
+ background: $color-secondary-2;
+ }
}
diff --git a/frontend/src/index.scss b/frontend/src/index.scss
index 9d6afc4..ea360be 100644
--- a/frontend/src/index.scss
+++ b/frontend/src/index.scss
@@ -77,3 +77,15 @@ a {
.popover-header {
color: $color-primary-1;
}
+
+@keyframes pulse {
+ 0% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0.3;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
diff --git a/frontend/src/logo.svg b/frontend/src/logo.svg
index 6b60c10..cb825f3 100644
--- a/frontend/src/logo.svg
+++ b/frontend/src/logo.svg
@@ -1,7 +1 @@
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/notification_controller.go b/notification_controller.go
index 3fa3c5b..b9b3b1c 100644
--- a/notification_controller.go
+++ b/notification_controller.go
@@ -27,10 +27,6 @@ import (
)
const (
- InsertNotification = "insert"
- UpdateNotification = "update"
- DeleteNotification = "delete"
-
writeWait = 10 * time.Second
pongWait = 60 * time.Second
pingPeriod = (pongWait * 9) / 10
@@ -122,8 +118,8 @@ func (wc *NotificationController) Run() {
}
}
-func (wc *NotificationController) Notify(event string, eventType string, message interface{}) {
- wc.broadcast <- gin.H{"event": event, "event_type": eventType, "message": message}
+func (wc *NotificationController) Notify(event string, message interface{}) {
+ wc.broadcast <- gin.H{"event": event, "message": message}
}
func (c *client) readPump() {
diff --git a/resources_controller.go b/resources_controller.go
index 10b31e3..0576e0f 100644
--- a/resources_controller.go
+++ b/resources_controller.go
@@ -98,7 +98,7 @@ func (csc *ResourcesController) Run() {
avg := Average(cpuPercent)
if avg > averageCPUPercentAlertThreshold && time.Now().Sub(lastAlertTime).Seconds() > averageCPUPercentAlertMinInterval {
- csc.notificationController.Notify("resources.cpu_alert", "alert", gin.H{
+ csc.notificationController.Notify("resources.cpu_alert", gin.H{
"cpu_percent": cpuPercent,
})
log.WithField("cpu_percent", cpuPercent).Warn("cpu percent usage has exceeded the limit threshold")
--
cgit v1.2.3-70-g09d2
From 5534413b3a3e6e783310be8147ac8340d3098a7e Mon Sep 17 00:00:00 2001
From: Emiliano Ciavatta
Date: Fri, 16 Oct 2020 15:09:05 +0200
Subject: Fix tests. General refactor
---
application_context_test.go | 7 +
connection_streams_controller.go | 4 +-
frontend/src/backend.js | 2 +-
frontend/src/components/Notifications.js | 2 +-
frontend/src/components/Notifications.scss | 2 +-
frontend/src/components/fields/ChoiceField.scss | 4 +-
frontend/src/components/fields/InputField.scss | 8 +-
frontend/src/components/fields/TagField.scss | 26 ++--
frontend/src/components/filters/AdvancedFilters.js | 2 +-
.../components/filters/RulesConnectionsFilter.js | 14 +-
.../components/filters/StringConnectionsFilter.js | 8 +-
frontend/src/components/pages/ConfigurationPage.js | 18 +--
frontend/src/components/panels/ConnectionsPane.js | 11 +-
frontend/src/components/panels/PcapsPane.js | 10 +-
frontend/src/components/panels/RulesPane.js | 90 +++++++------
frontend/src/components/panels/SearchPane.js | 10 +-
frontend/src/components/panels/StreamsPane.js | 4 +-
frontend/src/index.js | 3 -
frontend/src/index.scss | 2 +
frontend/src/log.js | 7 +-
frontend/src/serviceWorker.js | 141 ---------------------
frontend/src/setupTests.js | 5 -
frontend/src/utils.js | 2 +-
parsers/http_request_parser.go | 28 ++--
parsers/http_response_parser.go | 12 +-
parsers/parser.go | 4 +-
pcap_importer_test.go | 1 +
rules_manager.go | 4 +-
scripts/generate_ping.py | 2 +-
utils.go | 8 +-
30 files changed, 150 insertions(+), 291 deletions(-)
delete mode 100644 frontend/src/serviceWorker.js
delete mode 100644 frontend/src/setupTests.js
(limited to 'frontend/src/components/Notifications.scss')
diff --git a/application_context_test.go b/application_context_test.go
index a7f1a49..11d1ed4 100644
--- a/application_context_test.go
+++ b/application_context_test.go
@@ -35,6 +35,10 @@ func TestCreateApplicationContext(t *testing.T) {
assert.Nil(t, appContext.PcapImporter)
assert.Nil(t, appContext.RulesManager)
+ notificationController := NewNotificationController(appContext)
+ appContext.SetNotificationController(notificationController)
+ assert.Equal(t, notificationController, appContext.NotificationController)
+
config := Config{
ServerAddress: "10.10.10.10",
FlagRegex: "FLAG{test}",
@@ -58,11 +62,14 @@ func TestCreateApplicationContext(t *testing.T) {
checkAppContext, err := CreateApplicationContext(wrapper.Storage, "test")
assert.NoError(t, err)
+ checkAppContext.SetNotificationController(notificationController)
+ checkAppContext.Configure()
assert.True(t, checkAppContext.IsConfigured)
assert.Equal(t, checkAppContext.Config, config)
assert.Equal(t, checkAppContext.Accounts, accounts)
assert.NotNil(t, checkAppContext.PcapImporter)
assert.NotNil(t, checkAppContext.RulesManager)
+ assert.Equal(t, notificationController, appContext.NotificationController)
wrapper.Destroy(t)
}
diff --git a/connection_streams_controller.go b/connection_streams_controller.go
index eef1a2a..038c2c5 100644
--- a/connection_streams_controller.go
+++ b/connection_streams_controller.go
@@ -369,7 +369,7 @@ func decodePwntools(payload []byte, isClient bool, format string) string {
if isClient {
return fmt.Sprintf("p.send(%s)\n", content)
- } else {
- return fmt.Sprintf("p.recvuntil(%s)\n", content)
}
+
+ return fmt.Sprintf("p.recvuntil(%s)\n", content)
}
diff --git a/frontend/src/backend.js b/frontend/src/backend.js
index cc8604a..dc5089f 100644
--- a/frontend/src/backend.js
+++ b/frontend/src/backend.js
@@ -17,7 +17,7 @@
async function json(method, url, data, json, headers) {
const options = {
- method: method,
+ method,
body: json != null ? JSON.stringify(json) : data,
mode: "cors",
cache: "no-cache",
diff --git a/frontend/src/components/Notifications.js b/frontend/src/components/Notifications.js
index 92731d9..a81eba1 100644
--- a/frontend/src/components/Notifications.js
+++ b/frontend/src/components/Notifications.js
@@ -67,7 +67,7 @@ class Notifications extends Component {
n.variant = "blue";
return this.pushNotification(n);
default:
- return;
+ return null;
}
};
diff --git a/frontend/src/components/Notifications.scss b/frontend/src/components/Notifications.scss
index 98d228e..5852c7d 100644
--- a/frontend/src/components/Notifications.scss
+++ b/frontend/src/components/Notifications.scss
@@ -32,7 +32,7 @@
}
&.notification-open {
- transform: translateX(0px);
+ transform: translateX(0);
}
&.notification-closed {
diff --git a/frontend/src/components/fields/ChoiceField.scss b/frontend/src/components/fields/ChoiceField.scss
index c8c7ff1..85986af 100644
--- a/frontend/src/components/fields/ChoiceField.scss
+++ b/frontend/src/components/fields/ChoiceField.scss
@@ -27,8 +27,8 @@
}
.field-options {
- position: absolute;
- z-index: 20;
+ position: static;
+ z-index: 100;
top: 35px;
display: none;
width: 100%;
diff --git a/frontend/src/components/fields/InputField.scss b/frontend/src/components/fields/InputField.scss
index e8ef46a..eafb2ab 100644
--- a/frontend/src/components/fields/InputField.scss
+++ b/frontend/src/components/fields/InputField.scss
@@ -47,15 +47,15 @@
background-color: $color-primary-4 !important;
}
+ .file-label::after {
+ background-color: $color-secondary-4 !important;
+ }
+
.field-value input,
.field-value .file-label {
color: $color-primary-3 !important;
background-color: $color-primary-4 !important;
}
-
- .file-label::after {
- background-color: $color-secondary-4 !important;
- }
}
&.field-invalid {
diff --git a/frontend/src/components/fields/TagField.scss b/frontend/src/components/fields/TagField.scss
index 737f11f..723e71f 100644
--- a/frontend/src/components/fields/TagField.scss
+++ b/frontend/src/components/fields/TagField.scss
@@ -10,6 +10,18 @@
}
}
+ .react-tags {
+ position: relative;
+ display: flex;
+ border-radius: 4px;
+ background-color: $color-primary-2;
+
+ &:focus-within,
+ &:focus-within .react-tags__search-input {
+ background-color: $color-primary-1;
+ }
+ }
+
&.field-small {
font-size: 0.8em;
}
@@ -39,18 +51,6 @@
}
}
- .react-tags {
- position: relative;
- display: flex;
- border-radius: 4px;
- background-color: $color-primary-2;
-
- &:focus-within,
- &:focus-within .react-tags__search-input {
- background-color: $color-primary-1;
- }
- }
-
.react-tags__selected {
display: inline-block;
flex: 0 1;
@@ -154,4 +154,4 @@
cursor: auto;
opacity: 0.5;
}
-}
\ No newline at end of file
+}
diff --git a/frontend/src/components/filters/AdvancedFilters.js b/frontend/src/components/filters/AdvancedFilters.js
index 2b479ed..15667a5 100644
--- a/frontend/src/components/filters/AdvancedFilters.js
+++ b/frontend/src/components/filters/AdvancedFilters.js
@@ -33,7 +33,7 @@ class AdvancedFilters extends Component {
const active = ["client_address", "client_port", "min_duration", "max_duration", "min_bytes", "max_bytes"]
.some(f => this.urlParams.has(f));
if (this.state.active !== active) {
- this.setState({active: active});
+ this.setState({active});
}
};
dispatcher.register("connections_filters", this.connectionsFiltersCallback);
diff --git a/frontend/src/components/filters/RulesConnectionsFilter.js b/frontend/src/components/filters/RulesConnectionsFilter.js
index 86eae7e..37d36b7 100644
--- a/frontend/src/components/filters/RulesConnectionsFilter.js
+++ b/frontend/src/components/filters/RulesConnectionsFilter.js
@@ -35,17 +35,17 @@ class RulesConnectionsFilter extends Component {
const params = new URLSearchParams(this.props.location.search);
let activeRules = params.getAll("matched_rules") || [];
- backend.get("/api/rules").then(res => {
- let rules = res.json.flatMap(rule => rule.enabled ? [{id: rule.id, name: rule.name}] : []);
- activeRules = rules.filter(rule => activeRules.some(id => rule.id === id));
+ backend.get("/api/rules").then((res) => {
+ let rules = res.json.flatMap((rule) => rule.enabled ? [{id: rule.id, name: rule.name}] : []);
+ activeRules = rules.filter((rule) => activeRules.some(id => rule.id === id));
this.setState({rules, activeRules});
});
- this.connectionsFiltersCallback = payload => {
+ this.connectionsFiltersCallback = (payload) => {
if ("matched_rules" in payload && !_.isEqual(payload["matched_rules"].sort(), this.state.activeRules.sort())) {
- const newRules = this.state.rules.filter(r => payload["matched_rules"].includes(r.id));
+ const newRules = this.state.rules.filter((r) => payload["matched_rules"].includes(r.id));
this.setState({
- activeRules: newRules.map(r => {
+ activeRules: newRules.map((r) => {
return {id: r.id, name: r.name};
})
});
@@ -61,7 +61,7 @@ class RulesConnectionsFilter extends Component {
onChange = (activeRules) => {
if (!_.isEqual(activeRules.sort(), this.state.activeRules.sort())) {
this.setState({activeRules});
- dispatcher.dispatch("connections_filters", {"matched_rules": activeRules.map(r => r.id)});
+ dispatcher.dispatch("connections_filters", {"matched_rules": activeRules.map((r) => r.id)});
}
};
diff --git a/frontend/src/components/filters/StringConnectionsFilter.js b/frontend/src/components/filters/StringConnectionsFilter.js
index c3c5925..c5d7075 100644
--- a/frontend/src/components/filters/StringConnectionsFilter.js
+++ b/frontend/src/components/filters/StringConnectionsFilter.js
@@ -33,7 +33,7 @@ class StringConnectionsFilter extends Component {
let params = new URLSearchParams(this.props.location.search);
this.updateStateFromFilterValue(params.get(this.props.filterName));
- this.connectionsFiltersCallback = payload => {
+ this.connectionsFiltersCallback = (payload) => {
const name = this.props.filterName;
if (name in payload && this.state.filterValue !== payload[name]) {
this.updateStateFromFilterValue(payload[name]);
@@ -56,7 +56,7 @@ class StringConnectionsFilter extends Component {
fieldValue = this.props.replaceFunc(fieldValue);
}
if (this.isValueValid(fieldValue)) {
- this.setState({fieldValue, filterValue: filterValue});
+ this.setState({fieldValue, filterValue});
} else {
this.setState({fieldValue, invalidValue: true});
}
@@ -98,9 +98,9 @@ class StringConnectionsFilter extends Component {
}
this.setState({
- fieldValue: fieldValue,
+ fieldValue,
timeoutHandle: setTimeout(() => {
- this.setState({filterValue: filterValue});
+ this.setState({filterValue});
this.changeFilterValue(filterValue);
}, 500),
invalidValue: false
diff --git a/frontend/src/components/pages/ConfigurationPage.js b/frontend/src/components/pages/ConfigurationPage.js
index 2bd2da7..4f0ce21 100644
--- a/frontend/src/components/pages/ConfigurationPage.js
+++ b/frontend/src/components/pages/ConfigurationPage.js
@@ -59,11 +59,11 @@ class ConfigurationPage extends Component {
validateSettings = (settings) => {
let valid = true;
- if (!validation.isValidAddress(settings.config.server_address, true)) {
+ if (!validation.isValidAddress(settings.config["server_address"], true)) {
this.setState({serverAddressError: "invalid ip_address"});
valid = false;
}
- if (settings.config.flag_regex.length < 8) {
+ if (settings.config["flag_regex"].length < 8) {
this.setState({flagRegexError: "flag_regex.length < 8"});
valid = false;
}
@@ -84,7 +84,7 @@ class ConfigurationPage extends Component {
this.setState({
newUsername: "",
newPassword: "",
- settings: settings
+ settings
});
} else {
this.setState({
@@ -128,15 +128,15 @@ class ConfigurationPage extends Component {
- this.updateParam((s) => s.config.server_address = v)}/>
- this.updateParam((s) => s.config.flag_regex = v)}
+ onChange={(v) => 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)}/>
+ this.updateParam((s) => s.config["auth_required"] = v)}/>
diff --git a/frontend/src/components/panels/ConnectionsPane.js b/frontend/src/components/panels/ConnectionsPane.js
index 23c6114..9418fad 100644
--- a/frontend/src/components/panels/ConnectionsPane.js
+++ b/frontend/src/components/panels/ConnectionsPane.js
@@ -178,7 +178,7 @@ class ConnectionsPane extends Component {
let firstConnection = this.state.firstConnection;
let lastConnection = this.state.lastConnection;
- if (additionalParams !== undefined && additionalParams.from !== undefined && additionalParams.to === undefined) {
+ if (additionalParams && additionalParams.from && !additionalParams.to) {
if (res.length > 0) {
if (!isInitial) {
res = res.slice(1);
@@ -194,7 +194,7 @@ class ConnectionsPane extends Component {
firstConnection = connections[0];
}
}
- } else if (additionalParams !== undefined && additionalParams.to !== undefined && additionalParams.from === undefined) {
+ } else if (additionalParams && additionalParams.to && !additionalParams.from) {
if (res.length > 0) {
connections = res.slice(0, res.length - 1).concat(this.state.connections);
firstConnection = connections[0];
@@ -215,12 +215,7 @@ class ConnectionsPane extends Component {
}
}
- this.setState({
- loading: false,
- connections: connections,
- firstConnection: firstConnection,
- lastConnection: lastConnection
- });
+ this.setState({loading: false, connections, firstConnection, lastConnection});
if (firstConnection != null && lastConnection != null) {
dispatcher.dispatch("connection_updates", {
diff --git a/frontend/src/components/panels/PcapsPane.js b/frontend/src/components/panels/PcapsPane.js
index 64e7804..ddc5948 100644
--- a/frontend/src/components/panels/PcapsPane.js
+++ b/frontend/src/components/panels/PcapsPane.js
@@ -181,15 +181,15 @@ class PcapsPane extends Component {
};
const uploadCurlCommand = createCurlCommand("/pcap/upload", "POST", null, {
- file: "@" + ((this.state.uploadSelectedFile != null && this.state.isUploadFileValid) ?
+ "file": "@" + ((this.state.uploadSelectedFile != null && this.state.isUploadFileValid) ?
this.state.uploadSelectedFile.name : "invalid.pcap"),
- flush_all: this.state.uploadFlushAll
+ "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
+ "file": this.state.fileValue,
+ "flush_all": this.state.processFlushAll,
+ "delete_original_file": this.state.deleteOriginalFile
});
return (
diff --git a/frontend/src/components/panels/RulesPane.js b/frontend/src/components/panels/RulesPane.js
index d872b47..0bbd407 100644
--- a/frontend/src/components/panels/RulesPane.js
+++ b/frontend/src/components/panels/RulesPane.js
@@ -142,23 +142,23 @@ class RulesPane extends Component {
this.setState({ruleColorError: "color is not hexcolor"});
valid = false;
}
- if (!validation.isValidPort(rule.filter.service_port)) {
+ if (!validation.isValidPort(rule.filter["service_port"])) {
this.setState({ruleServicePortError: "service_port > 65565"});
valid = false;
}
- if (!validation.isValidPort(rule.filter.client_port)) {
+ if (!validation.isValidPort(rule.filter["client_port"])) {
this.setState({ruleClientPortError: "client_port > 65565"});
valid = false;
}
- if (!validation.isValidAddress(rule.filter.client_address)) {
+ 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) {
+ 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) {
+ if (rule.filter["min_bytes"] > rule.filter["max_bytes"]) {
this.setState({ruleBytesError: "min_bytes > max_bytes"});
valid = false;
}
@@ -175,9 +175,9 @@ class RulesPane extends Component {
const newPattern = _.cloneDeep(this.emptyPattern);
this.setState({
selectedRule: null,
- newRule: newRule,
+ newRule,
selectedPattern: null,
- newPattern: newPattern,
+ newPattern,
patternRegexFocused: false,
patternOccurrencesFocused: false,
ruleNameError: null,
@@ -210,9 +210,7 @@ class RulesPane extends Component {
const newPattern = _.cloneDeep(this.emptyPattern);
this.currentRule().patterns.push(pattern);
- this.setState({
- newPattern: newPattern
- });
+ this.setState({newPattern});
};
editPattern = (pattern) => {
@@ -237,7 +235,7 @@ class RulesPane extends Component {
valid = false;
this.setState({patternRegexFocused: true});
}
- if (pattern.min_occurrences > pattern.max_occurrences) {
+ if (pattern["min_occurrences"] > pattern["max_occurrences"]) {
valid = false;
this.setState({patternOccurrencesFocused: true});
}
@@ -273,25 +271,25 @@ class RulesPane extends Component {
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.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)}/>
+ onChange={(v) => this.updateParam(() => pattern["min_occurrences"] = v)}/>
- this.updateParam(() => pattern.max_occurrences = v)}/>
+ onChange={(v) => this.updateParam(() => pattern["max_occurrences"] = v)}/>
s", "s->c"]}
value={this.directions[pattern.direction]}
@@ -305,13 +303,13 @@ class RulesPane extends Component {
:
{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}
+ {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)}/> }
@@ -373,32 +371,32 @@ class RulesPane extends Component {
filters:
- this.updateParam((r) => r.filter.service_port = v)}
+ 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)}
+ 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)}/>
+ onChange={(v) => this.updateParam((r) => r.filter["client_address"] = v)}/>
- this.updateParam((r) => r.filter.min_duration = v)}/>
- this.updateParam((r) => r.filter["min_duration"] = v)}/>
+ this.updateParam((r) => r.filter.max_duration = v)}/>
- this.updateParam((r) => r.filter["max_duration"] = v)}/>
+ this.updateParam((r) => r.filter.min_bytes = v)}/>
- this.updateParam((r) => r.filter["min_bytes"] = v)}/>
+ this.updateParam((r) => r.filter.max_bytes = v)}/>
+ onChange={(v) => this.updateParam((r) => r.filter["max_bytes"] = v)}/>
diff --git a/frontend/src/components/panels/SearchPane.js b/frontend/src/components/panels/SearchPane.js
index d36e85e..776ebd0 100644
--- a/frontend/src/components/panels/SearchPane.js
+++ b/frontend/src/components/panels/SearchPane.js
@@ -70,20 +70,20 @@ class SearchPane extends Component {
loadSearches = () => {
backend.get("/api/searches")
- .then(res => this.setState({searches: res.json, searchesStatusCode: res.status}))
- .catch(res => this.setState({searchesStatusCode: res.status, searchesResponse: JSON.stringify(res.json)}));
+ .then((res) => this.setState({searches: res.json, searchesStatusCode: res.status}))
+ .catch((res) => this.setState({searchesStatusCode: res.status, searchesResponse: JSON.stringify(res.json)}));
};
performSearch = () => {
const options = this.state.currentSearchOptions;
this.setState({loading: true});
if (this.validateSearch(options)) {
- backend.post("/api/searches/perform", options).then(res => {
+ backend.post("/api/searches/perform", options).then((res) => {
this.reset();
this.setState({searchStatusCode: res.status, loading: false});
this.loadSearches();
this.viewSearch(res.json.id);
- }).catch(res => {
+ }).catch((res) => {
this.setState({
searchStatusCode: res.status, searchResponse: JSON.stringify(res.json),
loading: false
@@ -168,7 +168,7 @@ class SearchPane extends Component {
render() {
const options = this.state.currentSearchOptions;
- let searches = this.state.searches.map(s =>
+ let searches = this.state.searches.map((s) =>
{s.id.substring(0, 8)}
{this.extractPattern(s["search_options"])}
diff --git a/frontend/src/components/panels/StreamsPane.js b/frontend/src/components/panels/StreamsPane.js
index 41ab33d..1819fec 100644
--- a/frontend/src/components/panels/StreamsPane.js
+++ b/frontend/src/components/panels/StreamsPane.js
@@ -18,7 +18,7 @@
import DOMPurify from "dompurify";
import React, {Component} from "react";
import {Row} from "react-bootstrap";
-import ReactJson from "react-json-view"
+import ReactJson from "react-json-view";
import backend from "../../backend";
import log from "../../log";
import {downloadBlob, getHeaderValue} from "../../utils";
@@ -72,7 +72,7 @@ class StreamsPane extends Component {
setFormat = (format) => {
if (this.validFormats.includes(format)) {
- this.setState({format: format});
+ this.setState({format});
}
};
diff --git a/frontend/src/index.js b/frontend/src/index.js
index d00df88..62cb974 100644
--- a/frontend/src/index.js
+++ b/frontend/src/index.js
@@ -21,7 +21,6 @@ import ReactDOM from "react-dom";
import App from "./components/App";
import "./index.scss";
import notifications from "./notifications";
-import * as serviceWorker from "./serviceWorker";
notifications.createWebsocket();
@@ -31,5 +30,3 @@ ReactDOM.render(
// ,
document.getElementById("root")
);
-
-serviceWorker.unregister();
diff --git a/frontend/src/index.scss b/frontend/src/index.scss
index ea360be..1378d81 100644
--- a/frontend/src/index.scss
+++ b/frontend/src/index.scss
@@ -82,9 +82,11 @@ a {
0% {
opacity: 1;
}
+
50% {
opacity: 0.3;
}
+
100% {
opacity: 1;
}
diff --git a/frontend/src/log.js b/frontend/src/log.js
index 424e1b4..0afac47 100644
--- a/frontend/src/log.js
+++ b/frontend/src/log.js
@@ -16,9 +16,14 @@
*/
const log = {
- debug: (...obj) => console.info(...obj),
+ debug: (...obj) => isDevelopment() && console.info(...obj),
info: (...obj) => console.info(...obj),
+ warn: (...obj) => console.warn(...obj),
error: (...obj) => console.error(obj)
};
+function isDevelopment() {
+ return !process.env.NODE_ENV || process.env.NODE_ENV === 'development';
+}
+
export default log;
diff --git a/frontend/src/serviceWorker.js b/frontend/src/serviceWorker.js
deleted file mode 100644
index a1f0ba8..0000000
--- a/frontend/src/serviceWorker.js
+++ /dev/null
@@ -1,141 +0,0 @@
-// This optional code is used to register a service worker.
-// register() is not called by default.
-
-// This lets the app load faster on subsequent visits in production, and gives
-// it offline capabilities. However, it also means that developers (and users)
-// will only see deployed updates on subsequent visits to a page, after all the
-// existing tabs open on the page have been closed, since previously cached
-// resources are updated in the background.
-
-// To learn more about the benefits of this model and instructions on how to
-// opt-in, read https://bit.ly/CRA-PWA
-
-const isLocalhost = Boolean(
- window.location.hostname === "localhost" ||
- // [::1] is the IPv6 localhost address.
- window.location.hostname === "[::1]" ||
- // 127.0.0.0/8 are considered localhost for IPv4.
- window.location.hostname.match(
- /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
- )
-);
-
-export function register(config) {
- if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
- // The URL constructor is available in all browsers that support SW.
- const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
- if (publicUrl.origin !== window.location.origin) {
- // Our service worker won't work if PUBLIC_URL is on a different origin
- // from what our page is served on. This might happen if a CDN is used to
- // serve assets; see https://github.com/facebook/create-react-app/issues/2374
- return;
- }
-
- window.addEventListener("load", () => {
- const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
-
- if (isLocalhost) {
- // This is running on localhost. Let's check if a service worker still exists or not.
- checkValidServiceWorker(swUrl, config);
-
- // Add some additional logging to localhost, pointing developers to the
- // service worker/PWA documentation.
- navigator.serviceWorker.ready.then(() => {
- console.log(
- "This web app is being served cache-first by a service " +
- "worker. To learn more, visit https://bit.ly/CRA-PWA"
- );
- });
- } else {
- // Is not localhost. Just register service worker
- registerValidSW(swUrl, config);
- }
- });
- }
-}
-
-function registerValidSW(swUrl, config) {
- navigator.serviceWorker
- .register(swUrl)
- .then((registration) => {
- registration.onupdatefound = () => {
- const installingWorker = registration.installing;
- if (installingWorker == null) {
- return;
- }
- installingWorker.onstatechange = () => {
- if (installingWorker.state === "installed") {
- if (navigator.serviceWorker.controller) {
- // At this point, the updated precached content has been fetched,
- // but the previous service worker will still serve the older
- // content until all client tabs are closed.
- console.log(
- "New content is available and will be used when all " +
- "tabs for this page are closed. See https://bit.ly/CRA-PWA."
- );
-
- // Execute callback
- if (config && config.onUpdate) {
- config.onUpdate(registration);
- }
- } else {
- // At this point, everything has been precached.
- // It's the perfect time to display a
- // "Content is cached for offline use." message.
- console.log("Content is cached for offline use.");
-
- // Execute callback
- if (config && config.onSuccess) {
- config.onSuccess(registration);
- }
- }
- }
- };
- };
- })
- .catch((error) => {
- console.error("Error during service worker registration:", error);
- });
-}
-
-function checkValidServiceWorker(swUrl, config) {
- // Check if the service worker can be found. If it can't reload the page.
- fetch(swUrl, {
- headers: {"Service-Worker": "script"},
- })
- .then((response) => {
- // Ensure service worker exists, and that we really are getting a JS file.
- const contentType = response.headers.get("content-type");
- if (
- response.status === 404 ||
- (contentType != null && contentType.indexOf("javascript") === -1)
- ) {
- // No service worker found. Probably a different app. Reload the page.
- navigator.serviceWorker.ready.then((registration) => {
- registration.unregister().then(() => {
- window.location.reload();
- });
- });
- } else {
- // Service worker found. Proceed as normal.
- registerValidSW(swUrl, config);
- }
- })
- .catch(() => {
- console.log(
- "No internet connection found. App is running in offline mode."
- );
- });
-}
-
-export function unregister() {
- if ("serviceWorker" in navigator) {
- navigator.serviceWorker.ready
- .then((registration) => {
- registration.unregister();
- })
- .catch((error) => {
- console.error(error.message);
- });
- }
-}
diff --git a/frontend/src/setupTests.js b/frontend/src/setupTests.js
deleted file mode 100644
index 9b1d6d0..0000000
--- a/frontend/src/setupTests.js
+++ /dev/null
@@ -1,5 +0,0 @@
-// jest-dom adds custom jest matchers for asserting on DOM nodes.
-// allows you to do things like:
-// expect(element).toHaveTextContent(/react/i)
-// learn more: https://github.com/testing-library/jest-dom
-import "@testing-library/jest-dom/extend-expect";
\ No newline at end of file
diff --git a/frontend/src/utils.js b/frontend/src/utils.js
index 0f0927e..445e576 100644
--- a/frontend/src/utils.js
+++ b/frontend/src/utils.js
@@ -133,7 +133,7 @@ export function getHeaderValue(request, key) {
if (request && request.headers) {
return request.headers[Object.keys(request.headers).find((k) => k.toLowerCase() === key.toLowerCase())];
}
- return undefined;
+ return null;
}
export function downloadBlob(blob, fileName) {
diff --git a/parsers/http_request_parser.go b/parsers/http_request_parser.go
index 56093c9..d7ed81c 100644
--- a/parsers/http_request_parser.go
+++ b/parsers/http_request_parser.go
@@ -28,7 +28,7 @@ import (
"strings"
)
-type HttpRequestMetadata struct {
+type HTTPRequestMetadata struct {
BasicMetadata
Method string `json:"method"`
URL string `json:"url"`
@@ -40,19 +40,19 @@ type HttpRequestMetadata struct {
FormData map[string]string `json:"form_data" binding:"omitempty"`
Body string `json:"body" binding:"omitempty"`
Trailer map[string]string `json:"trailer" binding:"omitempty"`
- Reproducers HttpRequestMetadataReproducers `json:"reproducers"`
+ Reproducers HTTPRequestMetadataReproducers `json:"reproducers"`
}
-type HttpRequestMetadataReproducers struct {
+type HTTPRequestMetadataReproducers struct {
CurlCommand string `json:"curl_command"`
RequestsCode string `json:"requests_code"`
FetchRequest string `json:"fetch_request"`
}
-type HttpRequestParser struct {
+type HTTPRequestParser struct {
}
-func (p HttpRequestParser) TryParse(content []byte) Metadata {
+func (p HTTPRequestParser) TryParse(content []byte) Metadata {
reader := bufio.NewReader(bytes.NewReader(content))
request, err := http.ReadRequest(reader)
if err != nil {
@@ -68,7 +68,7 @@ func (p HttpRequestParser) TryParse(content []byte) Metadata {
_ = request.Body.Close()
_ = request.ParseForm()
- return HttpRequestMetadata{
+ return HTTPRequestMetadata{
BasicMetadata: BasicMetadata{"http-request"},
Method: request.Method,
URL: request.URL.String(),
@@ -80,7 +80,7 @@ func (p HttpRequestParser) TryParse(content []byte) Metadata {
FormData: JoinArrayMap(request.Form),
Body: body,
Trailer: JoinArrayMap(request.Trailer),
- Reproducers: HttpRequestMetadataReproducers{
+ Reproducers: HTTPRequestMetadataReproducers{
CurlCommand: curlCommand(content),
RequestsCode: requestsCode(request, body),
FetchRequest: fetchRequest(request, body),
@@ -92,17 +92,17 @@ func curlCommand(content []byte) string {
// a new reader is required because all the body is read before and GetBody() doesn't works
reader := bufio.NewReader(bytes.NewReader(content))
request, _ := http.ReadRequest(reader)
- if command, err := http2curl.GetCurlCommand(request); err == nil {
+ command, err := http2curl.GetCurlCommand(request)
+ if err == nil {
return command.String()
- } else {
- return err.Error()
}
+ return err.Error()
}
func requestsCode(request *http.Request, body string) string {
var b strings.Builder
- headers := toJson(JoinArrayMap(request.Header))
- cookies := toJson(CookiesMap(request.Cookies()))
+ headers := toJSON(JoinArrayMap(request.Header))
+ cookies := toJSON(CookiesMap(request.Cookies()))
b.WriteString("import requests\n\nresponse = requests." + strings.ToLower(request.Method) + "(")
b.WriteString("\"" + request.URL.String() + "\"")
@@ -146,14 +146,14 @@ func fetchRequest(request *http.Request, body string) string {
data["method"] = request.Method
// TODO: mode
- if jsonData := toJson(data); jsonData != "" {
+ if jsonData := toJSON(data); jsonData != "" {
return "fetch(\"" + request.URL.String() + "\", " + jsonData + ");"
} else {
return "invalid-request"
}
}
-func toJson(obj interface{}) string {
+func toJSON(obj interface{}) string {
if buffer, err := json.MarshalIndent(obj, "", "\t"); err == nil {
return string(buffer)
} else {
diff --git a/parsers/http_response_parser.go b/parsers/http_response_parser.go
index e5ef1ac..e61fffd 100644
--- a/parsers/http_response_parser.go
+++ b/parsers/http_response_parser.go
@@ -26,7 +26,7 @@ import (
"net/http"
)
-type HttpResponseMetadata struct {
+type HTTPResponseMetadata struct {
BasicMetadata
Status string `json:"status"`
StatusCode int `json:"status_code"`
@@ -40,10 +40,10 @@ type HttpResponseMetadata struct {
Trailer map[string]string `json:"trailer" binding:"omitempty"`
}
-type HttpResponseParser struct {
+type HTTPResponseParser struct {
}
-func (p HttpResponseParser) TryParse(content []byte) Metadata {
+func (p HTTPResponseParser) TryParse(content []byte) Metadata {
reader := bufio.NewReader(bytes.NewReader(content))
response, err := http.ReadResponse(reader, nil)
if err != nil {
@@ -74,11 +74,11 @@ func (p HttpResponseParser) TryParse(content []byte) Metadata {
_ = response.Body.Close()
var location string
- if locationUrl, err := response.Location(); err == nil {
- location = locationUrl.String()
+ if locationURL, err := response.Location(); err == nil {
+ location = locationURL.String()
}
- return HttpResponseMetadata{
+ return HTTPResponseMetadata{
BasicMetadata: BasicMetadata{"http-response"},
Status: response.Status,
StatusCode: response.StatusCode,
diff --git a/parsers/parser.go b/parsers/parser.go
index a29b1ab..9aca3b6 100644
--- a/parsers/parser.go
+++ b/parsers/parser.go
@@ -30,8 +30,8 @@ type BasicMetadata struct {
}
var parsers = []Parser{ // order matter
- HttpRequestParser{},
- HttpResponseParser{},
+ HTTPRequestParser{},
+ HTTPResponseParser{},
}
func Parse(content []byte) Metadata {
diff --git a/pcap_importer_test.go b/pcap_importer_test.go
index 8940060..a47f2d9 100644
--- a/pcap_importer_test.go
+++ b/pcap_importer_test.go
@@ -127,6 +127,7 @@ func newTestPcapImporter(wrapper *TestStorageWrapper, serverAddress string) *Pca
mAssemblers: sync.Mutex{},
mSessions: sync.Mutex{},
serverNet: *ParseIPNet(serverAddress),
+ notificationController: NewNotificationController(nil),
}
}
diff --git a/rules_manager.go b/rules_manager.go
index 327e4ec..a6d969f 100644
--- a/rules_manager.go
+++ b/rules_manager.go
@@ -328,10 +328,10 @@ func (rm *rulesManagerImpl) validateAndAddRuleLocal(rule *Rule) error {
duplicatePatterns[regex] = true
}
- startId := len(rm.patterns)
+ startID := len(rm.patterns)
for id, pattern := range newPatterns {
rm.patterns = append(rm.patterns, pattern)
- rm.patternsIds[pattern.String()] = uint(startId + id)
+ rm.patternsIds[pattern.String()] = uint(startID + id)
}
rm.rules[rule.ID] = *rule
diff --git a/scripts/generate_ping.py b/scripts/generate_ping.py
index 73454c1..0c06a9e 100755
--- a/scripts/generate_ping.py
+++ b/scripts/generate_ping.py
@@ -28,7 +28,7 @@ if __name__ == "__main__":
port = 9999
n = 10000
-
+
if sys.argv[1] == "server":
# docker run -it --rm -p 9999:9999 -v "$PWD":/ping -w /ping python:3 python generate_ping.py server
with socketserver.TCPServer(("0.0.0.0", port), PongHandler) as server:
diff --git a/utils.go b/utils.go
index 639fd94..e721d78 100644
--- a/utils.go
+++ b/utils.go
@@ -57,12 +57,12 @@ func CustomRowID(payload uint64, timestamp time.Time) RowID {
binary.BigEndian.PutUint32(key[0:4], uint32(timestamp.Unix()))
binary.BigEndian.PutUint64(key[4:12], payload)
- if oid, err := primitive.ObjectIDFromHex(hex.EncodeToString(key[:])); err == nil {
+ oid, err := primitive.ObjectIDFromHex(hex.EncodeToString(key[:]))
+ if err == nil {
return oid
- } else {
- log.WithError(err).Warn("failed to create object id")
- return primitive.NewObjectID()
}
+ log.WithError(err).Warn("failed to create object id")
+ return primitive.NewObjectID()
}
func NewRowID() RowID {
--
cgit v1.2.3-70-g09d2