aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--application_router.go3
-rw-r--r--frontend/src/components/Connection.js31
-rw-r--r--frontend/src/components/Connection.scss4
-rw-r--r--frontend/src/components/ConnectionContent.js31
-rw-r--r--frontend/src/components/ServicePortFilter.js91
-rw-r--r--frontend/src/components/ServicePortFilter.scss61
-rw-r--r--frontend/src/views/App.js1
-rw-r--r--frontend/src/views/Connections.js86
-rw-r--r--frontend/src/views/Header.js15
-rw-r--r--frontend/src/views/Header.scss10
10 files changed, 271 insertions, 62 deletions
diff --git a/application_router.go b/application_router.go
index bd876da..9f497e8 100644
--- a/application_router.go
+++ b/application_router.go
@@ -19,6 +19,9 @@ func CreateApplicationRouter(applicationContext *ApplicationContext) *gin.Engine
router.MaxMultipartMemory = 8 << 30
router.Use(static.Serve("/", static.LocalFile("./frontend/build", true)))
+ router.GET("/connections/:id", func(c *gin.Context) {
+ c.File("./frontend/build/index.html")
+ })
router.POST("/setup", func(c *gin.Context) {
if applicationContext.IsConfigured {
diff --git a/frontend/src/components/Connection.js b/frontend/src/components/Connection.js
index 8121d51..e41f542 100644
--- a/frontend/src/components/Connection.js
+++ b/frontend/src/components/Connection.js
@@ -67,6 +67,9 @@ class Connection extends Component {
if (this.props.selected) {
classes += " connection-selected";
}
+ if (this.props.containsFlag) {
+ classes += " contains-flag";
+ }
const popoverFor = function (name, content) {
return <Popover id={`popover-${name}-${conn.id}`} className="connection-popover">
@@ -93,27 +96,27 @@ class Connection extends Component {
<span className="connection-service">
<Button size="sm" style={{
"backgroundColor": serviceColor
- }}>{serviceName}</Button>
+ }} onClick={() => this.props.addServicePortFilter(conn.port_dst)}>{serviceName}</Button>
</span>
</td>
- <td className="clickable" onClick={() => this.props.onSelected()}>{conn.ip_src}</td>
- <td className="clickable" onClick={() => this.props.onSelected()}>{conn.port_src}</td>
- <td className="clickable" onClick={() => this.props.onSelected()}>{conn.ip_dst}</td>
- <td className="clickable" onClick={() => this.props.onSelected()}>{conn.port_dst}</td>
- <td className="clickable" onClick={() => this.props.onSelected()}>
+ <td className="clickable" onClick={this.props.onSelected}>{conn.ip_src}</td>
+ <td className="clickable" onClick={this.props.onSelected}>{conn.port_src}</td>
+ <td className="clickable" onClick={this.props.onSelected}>{conn.ip_dst}</td>
+ <td className="clickable" onClick={this.props.onSelected}>{conn.port_dst}</td>
+ <td className="clickable" onClick={this.props.onSelected}>
<OverlayTrigger trigger={["focus", "hover"]} placement="right"
overlay={popoverFor("duration", timeInfo)}>
<span className="test-tooltip">{duration}s</span>
</OverlayTrigger>
</td>
- <td className="clickable" onClick={() => this.props.onSelected()}>{conn.client_bytes}</td>
- <td className="clickable" onClick={() => this.props.onSelected()}>{conn.server_bytes}</td>
- <td>
- <OverlayTrigger trigger={["focus", "hover"]} placement="right"
- overlay={popoverFor("hide", <span>Hide this connection from the list</span>)}>
- <span className={"connection-icon" + (conn.hidden ? " icon-enabled" : "")}
- onClick={() => this.handleAction("hide")}>%</span>
- </OverlayTrigger>
+ <td className="clickable" onClick={this.props.onSelected}>{conn.client_bytes}</td>
+ <td className="clickable" onClick={this.props.onSelected}>{conn.server_bytes}</td>
+ <td className="contains-flag">
+ {/*<OverlayTrigger trigger={["focus", "hover"]} placement="right"*/}
+ {/* overlay={popoverFor("hide", <span>Hide this connection from the list</span>)}>*/}
+ {/* <span className={"connection-icon" + (conn.hidden ? " icon-enabled" : "")}*/}
+ {/* onClick={() => this.handleAction("hide")}>%</span>*/}
+ {/*</OverlayTrigger>*/}
<OverlayTrigger trigger={["focus", "hover"]} placement="right"
overlay={popoverFor("hide", <span>Mark this connection</span>)}>
<span className={"connection-icon" + (conn.marked ? " icon-enabled" : "")}
diff --git a/frontend/src/components/Connection.scss b/frontend/src/components/Connection.scss
index 5ad195d..7c8e00c 100644
--- a/frontend/src/components/Connection.scss
+++ b/frontend/src/components/Connection.scss
@@ -39,6 +39,10 @@
background-color: $color-primary-2;
}
+ &.contains-flag {
+ border-right: 3px solid $color-secondary-2;
+ }
+
}
.connection-popover {
diff --git a/frontend/src/components/ConnectionContent.js b/frontend/src/components/ConnectionContent.js
index 2028158..40fdcad 100644
--- a/frontend/src/components/ConnectionContent.js
+++ b/frontend/src/components/ConnectionContent.js
@@ -1,9 +1,7 @@
import React, {Component} from 'react';
import './ConnectionContent.scss';
-import {Col, Container, Dropdown, Row} from 'react-bootstrap';
+import {Dropdown} from 'react-bootstrap';
import axios from 'axios';
-import {withRouter} from "react-router-dom";
-import {Redirect} from "react-router";
class ConnectionContent extends Component {
@@ -19,12 +17,6 @@ class ConnectionContent extends Component {
this.setFormat = this.setFormat.bind(this);
}
- componentDidMount() {
- if ('format' in this.props.match.params) {
- this.setFormat(this.props.match.params.format);
- }
- }
-
componentDidUpdate(prevProps, prevState, snapshot) {
if (this.props.connection !== null && (
this.props.connection !== prevProps.connection || this.state.format !== prevState.format)) {
@@ -51,11 +43,8 @@ class ConnectionContent extends Component {
return <div>nope</div>;
}
- const format = this.state.format !== "default" ? `/${this.state.format}` : "";
- const redirect = <Redirect push to={`/connections/${this.props.connection.id}${format}`}/>;
-
let payload = content.map((c, i) =>
- <span key={`content-${i}`} className={c.from_client ? "from-client" : "from-server"} title="cccccc">
+ <span key={`content-${i}`} className={c.from_client ? "from-client" : "from-server"}>
{c.content}
</span>
);
@@ -63,11 +52,11 @@ class ConnectionContent extends Component {
return (
<div className="connection-content">
<div className="connection-content-options">
- <Container>
- <Row>
- <Col md={2}>ciao</Col>
- </Row>
- </Container>
+ {/*<Container>*/}
+ {/* <Row>*/}
+ {/* <Col md={2}>ciao</Col>*/}
+ {/* </Row>*/}
+ {/*</Container>*/}
<Dropdown onSelect={this.setFormat} >
@@ -92,10 +81,6 @@ class ConnectionContent extends Component {
</div>
<pre>{payload}</pre>
-
- {/*{redirect}*/}
-
-
</div>
);
}
@@ -103,4 +88,4 @@ class ConnectionContent extends Component {
}
-export default withRouter(ConnectionContent);
+export default ConnectionContent;
diff --git a/frontend/src/components/ServicePortFilter.js b/frontend/src/components/ServicePortFilter.js
new file mode 100644
index 0000000..72f2643
--- /dev/null
+++ b/frontend/src/components/ServicePortFilter.js
@@ -0,0 +1,91 @@
+import React, {Component} from 'react';
+import './ServicePortFilter.scss';
+import {withRouter} from "react-router-dom";
+import {Redirect} from "react-router";
+
+class ServicePortFilter extends Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ servicePort: "",
+ servicePortUrl: null,
+ timeoutHandle: null
+ };
+
+ this.servicePortChanged = this.servicePortChanged.bind(this);
+ }
+
+ componentDidMount() {
+ let params = new URLSearchParams(this.props.location.search);
+ let servicePort = params.get("service_port");
+ if (servicePort !== null) {
+ this.setState({
+ servicePort: servicePort,
+ servicePortUrl: servicePort
+ });
+ }
+ }
+
+ servicePortChanged(event) {
+ let value = event.target.value.replace(/[^\d]/gi, '');
+ if (value.startsWith("0")) {
+ return;
+ }
+ if (value !== "") {
+ let port = parseInt(value);
+ if (port > 65565) {
+ return;
+ }
+ }
+
+ if (this.state.timeoutHandle !== null) {
+ clearTimeout(this.state.timeoutHandle);
+ }
+ this.setState({
+ servicePort: value,
+ timeoutHandle: setTimeout(() =>
+ this.setState({servicePortUrl: value === "" ? null : value}), 300)
+ });
+ }
+
+ render() {
+ let redirect = null;
+ let urlParams = new URLSearchParams(this.props.location.search);
+ if (urlParams.get("service_port") !== this.state.servicePortUrl) {
+ if (this.state.servicePortUrl !== null) {
+ urlParams.set("service_port", this.state.servicePortUrl);
+ } else {
+ urlParams.delete("service_port");
+ }
+ redirect = <Redirect push to={`${this.props.location.pathname}?${urlParams}`} />;
+ }
+ let active = this.state.servicePort !== "";
+
+ return (
+ <div className={"filter d-inline-block" + (active ? " filter-active" : "")}
+ style={{"width": "200px"}}>
+ <div className="input-group">
+ <div className="filter-name-wrapper">
+ <span className="filter-name" id="filter-service_port">service_port:</span>
+ </div>
+ <input placeholder="all ports" aria-label="service_port" aria-describedby="filter-service_port"
+ className="form-control filter-value" onChange={this.servicePortChanged} value={this.state.servicePort} /></div>
+
+ { active &&
+ <div className="filter-delete">
+ <span className="filter-delete-icon" onClick={() => this.setState({
+ servicePort: "",
+ servicePortUrl: null
+ })}>del</span>
+ </div>
+ }
+
+ {redirect}
+ </div>
+ );
+ }
+
+}
+
+export default withRouter(ServicePortFilter);
diff --git a/frontend/src/components/ServicePortFilter.scss b/frontend/src/components/ServicePortFilter.scss
new file mode 100644
index 0000000..2b23444
--- /dev/null
+++ b/frontend/src/components/ServicePortFilter.scss
@@ -0,0 +1,61 @@
+@import '../colors.scss';
+
+.filter {
+ margin: 0 10px;
+ position: relative;
+
+ .filter-name-wrapper {
+ background-color: $color-primary-2;
+ padding: 3px 7px;
+ border-top-left-radius: 5px;
+ border-bottom-left-radius: 5px;
+ }
+
+ .filter-name {
+ font-size: 13px;
+ }
+
+ .filter-value {
+ font-size: 13px;
+ padding-left: 0;
+ border-radius: 5px;
+
+ &:focus {
+ background-color: $color-primary-2;
+ }
+ }
+
+ &.filter-active {
+ .filter-name-wrapper {
+ background-color: $color-primary-4;
+ color: $color-primary-3;
+ }
+
+ .filter-value {
+ font-size: 13px;
+ background-color: $color-primary-4;
+ color: $color-primary-3;
+
+ &:focus {
+ background-color: $color-primary-4;
+ }
+ }
+ }
+
+ .filter-delete {
+ position: absolute;
+ right: 10px;
+ top: 10px;
+
+ z-index: 10;
+ font-size: 11px;
+ letter-spacing: -0.5px;
+
+ color: $color-primary-2;
+ cursor: pointer;
+
+ .filter-delete-icon {
+ font-weight: 800;
+ }
+ }
+} \ No newline at end of file
diff --git a/frontend/src/views/App.js b/frontend/src/views/App.js
index 34e980e..e3119aa 100644
--- a/frontend/src/views/App.js
+++ b/frontend/src/views/App.js
@@ -26,7 +26,6 @@ class App extends Component {
<Router>
<Header onOpenServices={() => this.setState({servicesShow: true})}/>
<Switch>
- <Route path="/connections/:id/:format" children={<MainPane/>}/>
<Route path="/connections/:id" children={<MainPane/>}/>
<Route path="/" children={<MainPane/>}/>
</Switch>
diff --git a/frontend/src/views/Connections.js b/frontend/src/views/Connections.js
index e4a7b01..9b9fe35 100644
--- a/frontend/src/views/Connections.js
+++ b/frontend/src/views/Connections.js
@@ -5,7 +5,6 @@ import Connection from "../components/Connection";
import Table from 'react-bootstrap/Table';
import {Redirect} from 'react-router';
import {withRouter} from "react-router-dom";
-import {objectToQueryString} from "../utils";
class Connections extends Component {
@@ -16,7 +15,11 @@ class Connections extends Component {
connections: [],
firstConnection: null,
lastConnection: null,
- showHidden: false
+ showHidden: false,
+ prevParams: null,
+ flagRule: null,
+ rules: null,
+ queryString: null
};
this.scrollTopThreashold = 0.00001;
@@ -26,10 +29,12 @@ class Connections extends Component {
this.handleScroll = this.handleScroll.bind(this);
this.connectionSelected = this.connectionSelected.bind(this);
+ this.addServicePortFilter = this.addServicePortFilter.bind(this);
}
componentDidMount() {
- this.loadConnections({limit: this.queryLimit, hidden: this.state.showHidden});
+ this.loadConnections({limit: this.queryLimit, hidden: this.state.showHidden})
+ .then(() => this.setState({loaded: true}));
}
connectionSelected(c) {
@@ -37,7 +42,13 @@ class Connections extends Component {
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, hidden: this.state.showHidden})
+ .then(() => console.log("Connections reloaded after query string update"));
+ }
+ }
handleScroll(e) {
let relativeScroll = e.currentTarget.scrollTop / (e.currentTarget.scrollHeight - e.currentTarget.clientHeight);
@@ -45,32 +56,38 @@ class Connections extends Component {
this.loadConnections({
from: this.state.lastConnection.id, limit: this.queryLimit,
hidden: this.state.showHidden
- });
+ }).then(() => console.log("Following connections loaded"));
}
if (!this.state.loading && relativeScroll < this.scrollTopThreashold) {
this.loadConnections({
to: this.state.firstConnection.id, limit: this.queryLimit,
hidden: this.state.showHidden
- });
+ }).then(() => console.log("Previous connections loaded"));
}
+ }
+ addServicePortFilter(port) {
+ let urlParams = new URLSearchParams(this.props.location.search);
+ urlParams.set("service_port", port);
+ this.setState({queryString: "?" + urlParams});
}
async loadConnections(params) {
let url = "/api/connections";
- if (params !== undefined) {
- const urlParams = new URLSearchParams(window.location.search);
- let obj = Object.fromEntries(urlParams.entries());
- url += "?" + objectToQueryString({...params, ...obj}); // TODO: remove this shit
+ const urlParams = new URLSearchParams(this.props.location.search);
+ for (const [name, value] of Object.entries(params)) {
+ urlParams.set(name, value);
}
- this.setState({loading: true});
- let res = await axios.get(url);
+
+ this.setState({loading: true, prevParams: params});
+ let res = await axios.get(`${url}?${urlParams}`);
let connections = this.state.connections;
let firstConnection = this.state.firstConnection;
let lastConnection = this.state.lastConnection;
- if (res.data.length > 0) {
- if (params !== undefined && params.from !== undefined) {
+
+ if (params !== undefined && params.from !== undefined) {
+ if (res.data.length > 0) {
connections = this.state.connections.concat(res.data);
lastConnection = connections[connections.length - 1];
if (connections.length > this.maxConnections) {
@@ -78,34 +95,55 @@ class Connections extends Component {
connections.length - 1);
firstConnection = connections[0];
}
- } else if (params !== undefined && params.to !== undefined) {
+ }
+ } else if (params !== undefined && params.to !== undefined) {
+ if (res.data.length > 0) {
connections = res.data.concat(this.state.connections);
firstConnection = connections[0];
if (connections.length > this.maxConnections) {
connections = connections.slice(0, this.maxConnections);
lastConnection = connections[this.maxConnections - 1];
}
- } else {
+ }
+ } else {
+ if (res.data.length > 0) {
connections = res.data;
firstConnection = connections[0];
lastConnection = connections[connections.length - 1];
+ } else {
+ connections = [];
+ firstConnection = null;
+ lastConnection = null;
}
}
+ let flagRule = this.state.flagRule;
+ let rules = this.state.rules;
+ if (flagRule === null) {
+ let res = await axios.get("/api/rules");
+ rules = res.data;
+ flagRule = rules.filter(rule => {
+ return rule.name === "flag";
+ })[0];
+ }
+
this.setState({
loading: false,
connections: connections,
+ rules: res.data,
+ flagRule: flagRule,
firstConnection: firstConnection,
lastConnection: lastConnection
});
}
-
render() {
- let redirect = null;
+ let redirect;
+ let queryString = this.state.queryString !== null ? this.state.queryString : ""
if (this.state.selected) {
- const format = this.props.match.params.format;
- redirect = <Redirect push to={`/connections/${this.state.selected}${format !== undefined ? ("/" + format) : ""}`} />;
+ let format = this.props.match.params.format;
+ format = format !== undefined ? "/" + format : "";
+ redirect = <Redirect push to={`/connections/${this.state.selected}${format}${queryString}`} />;
}
let loading = null;
@@ -123,8 +161,8 @@ class Connections extends Component {
<tr>
<th>service</th>
<th>srcip</th>
- <th>dstip</th>
<th>srcport</th>
+ <th>dstip</th>
<th>dstport</th>
<th>duration</th>
<th>up</th>
@@ -137,14 +175,16 @@ class Connections extends Component {
this.state.connections.map(c =>
<Connection key={c.id} data={c} onSelected={() => this.connectionSelected(c)}
selected={this.state.selected === c.id} onMarked={marked => c.marked = marked}
- onEnabled={enabled => c.hidden = !enabled}/>
+ onEnabled={enabled => c.hidden = !enabled}
+ containsFlag={c.matched_rules.includes(this.state.flagRule.id)}
+ addServicePortFilter={this.addServicePortFilter}/>
)
}
{loading}
</tbody>
</Table>
- {/*{redirect}*/}
+ {redirect}
</div>
);
}
diff --git a/frontend/src/views/Header.js b/frontend/src/views/Header.js
index f96b036..5118ec3 100644
--- a/frontend/src/views/Header.js
+++ b/frontend/src/views/Header.js
@@ -2,6 +2,7 @@ import React, {Component} from 'react';
import Typed from 'typed.js';
import './Header.scss';
import {Button} from "react-bootstrap";
+import ServicePortFilter from "../components/ServicePortFilter";
class Header extends Component {
@@ -29,13 +30,25 @@ class Header extends Component {
return (
<header className="header container-fluid">
<div className="row">
- <div className="col">
+ <div className="col-auto">
<h1 className="header-title type-wrap">
<span style={{whiteSpace: 'pre'}} ref={(el) => {
this.el = el;
}}/>
</h1>
</div>
+
+ <div className="col-auto">
+ <div className="filters-bar-wrapper">
+ <div className="filters-bar">
+ <ServicePortFilter />
+ {/*<ServicePortFilter name="started_before" default="infinity" />*/}
+ {/*<ServicePortFilter name="started_after" default="-infinity" />*/}
+
+ </div>
+ </div>
+ </div>
+
<div className="col">
<div className="header-buttons">
<Button variant="yellow" size="sm">pcaps</Button>
diff --git a/frontend/src/views/Header.scss b/frontend/src/views/Header.scss
index e5040d8..806e398 100644
--- a/frontend/src/views/Header.scss
+++ b/frontend/src/views/Header.scss
@@ -2,6 +2,7 @@
.header {
padding: 15px 30px;
+ height: 80px;
> .row {
background-color: $color-primary-0;
@@ -9,10 +10,19 @@
.header-title {
margin: 5px 0 5px -5px;
+ width: 200px;
}
.header-buttons {
margin: 5px 0;
text-align: right;
}
+
+ .filters-bar-wrapper {
+ height: 50px;
+ padding: 8px 0;
+
+ .filters-bar {
+ }
+ }
}