aboutsummaryrefslogtreecommitdiff
path: root/frontend/src/components/panels
diff options
context:
space:
mode:
authorEmiliano Ciavatta2020-10-16 12:16:44 +0000
committerEmiliano Ciavatta2020-10-16 12:16:44 +0000
commitd4bac2d6741f7a291522c29c9ecc87c3e32e21d4 (patch)
treefd48e9b0fa10f0a0c72adcc8f0f9709a5af206ee /frontend/src/components/panels
parent2fb8993008752063fa13f253784e9e92552e339d (diff)
Add notification when pcap have been processed
Diffstat (limited to 'frontend/src/components/panels')
-rw-r--r--frontend/src/components/panels/ConnectionsPane.js107
-rw-r--r--frontend/src/components/panels/PcapsPane.js40
-rw-r--r--frontend/src/components/panels/RulesPane.js51
-rw-r--r--frontend/src/components/panels/SearchPane.js34
-rw-r--r--frontend/src/components/panels/ServicesPane.js68
5 files changed, 169 insertions, 131 deletions
diff --git a/frontend/src/components/panels/ConnectionsPane.js b/frontend/src/components/panels/ConnectionsPane.js
index ea47059..23c6114 100644
--- a/frontend/src/components/panels/ConnectionsPane.js
+++ b/frontend/src/components/panels/ConnectionsPane.js
@@ -15,20 +15,20 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-import React, {Component} from 'react';
-import './ConnectionsPane.scss';
-import Connection from "../objects/Connection";
-import Table from 'react-bootstrap/Table';
+import React, {Component} from "react";
+import Table from "react-bootstrap/Table";
+import {Redirect} from "react-router";
import {withRouter} from "react-router-dom";
import backend from "../../backend";
-import ConnectionMatchedRules from "../objects/ConnectionMatchedRules";
-import log from "../../log";
-import ButtonField from "../fields/ButtonField";
import dispatcher from "../../dispatcher";
-import {Redirect} from "react-router";
+import log from "../../log";
import {updateParams} from "../../utils";
+import ButtonField from "../fields/ButtonField";
+import Connection from "../objects/Connection";
+import ConnectionMatchedRules from "../objects/ConnectionMatchedRules";
+import "./ConnectionsPane.scss";
-const classNames = require('classnames');
+const classNames = require("classnames");
class ConnectionsPane extends Component {
@@ -67,55 +67,56 @@ class ConnectionsPane extends Component {
this.loadConnections(additionalParams, urlParams, true).then(() => log.debug("Connections loaded"));
- this.connectionsFiltersCallback = payload => {
- const newParams = updateParams(this.state.urlParams, payload);
- if (this.state.urlParams.toString() === newParams.toString()) {
- return;
- }
-
- log.debug("Update following url params:", payload);
- this.queryStringRedirect = true;
- this.setState({urlParams: newParams});
-
- this.loadConnections({limit: this.queryLimit}, newParams)
- .then(() => log.info("ConnectionsPane reloaded after query string update"));
- };
- dispatcher.register("connections_filters", this.connectionsFiltersCallback);
-
- this.timelineUpdatesCallback = payload => {
- this.connectionsListRef.current.scrollTop = 0;
- this.loadConnections({
- "started_after": Math.round(payload.from.getTime() / 1000),
- "started_before": Math.round(payload.to.getTime() / 1000),
- limit: this.maxConnections
- }).then(() => log.info(`Loading connections between ${payload.from} and ${payload.to}`));
- };
- dispatcher.register("timeline_updates", this.timelineUpdatesCallback);
-
- this.notificationsCallback = payload => {
- if (payload.event === "rules.new" || payload.event === "rules.edit") {
- this.loadRules().then(() => log.debug("Loaded connection rules after notification update"));
- }
- if (payload.event === "services.edit") {
- this.loadServices().then(() => log.debug("Services reloaded after notification update"));
- }
- };
- dispatcher.register("notifications", this.notificationsCallback);
-
- this.pulseConnectionsViewCallback = payload => {
- this.setState({pulseConnectionsView: true});
- setTimeout(() => this.setState({pulseConnectionsView: false}), payload.duration);
- };
- dispatcher.register("pulse_connections_view", this.pulseConnectionsViewCallback);
+ dispatcher.register("connections_filters", this.handleConnectionsFilters);
+ dispatcher.register("timeline_updates", this.handleTimelineUpdates);
+ dispatcher.register("notifications", this.handleNotifications);
+ dispatcher.register("pulse_connections_view", this.handlePulseConnectionsView);
}
componentWillUnmount() {
- dispatcher.unregister(this.timelineUpdatesCallback);
- dispatcher.unregister(this.notificationsCallback);
- dispatcher.unregister(this.pulseConnectionsViewCallback);
- dispatcher.unregister(this.connectionsFiltersCallback);
+ dispatcher.unregister(this.handleConnectionsFilters);
+ dispatcher.unregister(this.handleTimelineUpdates);
+ dispatcher.unregister(this.handleNotifications);
+ dispatcher.unregister(this.handlePulseConnectionsView);
}
+ handleConnectionsFilters = (payload) => {
+ const newParams = updateParams(this.state.urlParams, payload);
+ if (this.state.urlParams.toString() === newParams.toString()) {
+ return;
+ }
+
+ log.debug("Update following url params:", payload);
+ this.queryStringRedirect = true;
+ this.setState({urlParams: newParams});
+
+ this.loadConnections({limit: this.queryLimit}, newParams)
+ .then(() => log.info("ConnectionsPane reloaded after query string update"));
+ };
+
+ handleTimelineUpdates = (payload) => {
+ this.connectionsListRef.current.scrollTop = 0;
+ this.loadConnections({
+ "started_after": Math.round(payload.from.getTime() / 1000),
+ "started_before": Math.round(payload.to.getTime() / 1000),
+ limit: this.maxConnections
+ }).then(() => log.info(`Loading connections between ${payload.from} and ${payload.to}`));
+ };
+
+ handleNotifications = (payload) => {
+ if (payload.event === "rules.new" || payload.event === "rules.edit") {
+ this.loadRules().then(() => log.debug("Loaded connection rules after notification update"));
+ }
+ if (payload.event === "services.edit") {
+ this.loadServices().then(() => log.debug("Services reloaded after notification update"));
+ }
+ };
+
+ handlePulseConnectionsView = (payload) => {
+ this.setState({pulseConnectionsView: true});
+ setTimeout(() => this.setState({pulseConnectionsView: false}), payload.duration);
+ };
+
connectionSelected = (c) => {
this.connectionSelectedRedirect = true;
this.setState({selected: c.id});
diff --git a/frontend/src/components/panels/PcapsPane.js b/frontend/src/components/panels/PcapsPane.js
index fd3db75..64e7804 100644
--- a/frontend/src/components/panels/PcapsPane.js
+++ b/frontend/src/components/panels/PcapsPane.js
@@ -24,6 +24,7 @@ import ButtonField from "../fields/ButtonField";
import CheckField from "../fields/CheckField";
import InputField from "../fields/InputField";
import TextField from "../fields/TextField";
+import CopyLinkPopover from "../objects/CopyLinkPopover";
import LinkPopover from "../objects/LinkPopover";
import "./common.scss";
import "./PcapsPane.scss";
@@ -44,16 +45,20 @@ class PcapsPane extends Component {
componentDidMount() {
this.loadSessions();
-
- dispatcher.register("notifications", (payload) => {
- if (payload.event === "pcap.upload" || payload.event === "pcap.file") {
- this.loadSessions();
- }
- });
-
+ dispatcher.register("notifications", this.handleNotifications);
document.title = "caronte:~/pcaps$";
}
+ componentWillUnmount() {
+ dispatcher.unregister(this.handleNotifications);
+ }
+
+ handleNotifications = (payload) => {
+ if (payload.event.startsWith("pcap")) {
+ this.loadSessions();
+ }
+ };
+
loadSessions = () => {
backend.get("/api/pcap/sessions")
.then((res) => this.setState({sessions: res.json, sessionsStatusCode: res.status}))
@@ -130,10 +135,19 @@ class PcapsPane extends Component {
};
render() {
- let sessions = this.state.sessions.map((s) =>
- <tr key={s.id} className="row-small row-clickable">
- <td>{s["id"].substring(0, 8)}</td>
- <td>{dateTimeToTime(s["started_at"])}</td>
+ let sessions = this.state.sessions.map((s) => {
+ const startedAt = new Date(s["started_at"]);
+ const completedAt = new Date(s["completed_at"]);
+ let timeInfo = <div>
+ <span>Started at {startedAt.toLocaleDateString() + " " + startedAt.toLocaleTimeString()}</span><br/>
+ <span>Completed at {completedAt.toLocaleDateString() + " " + completedAt.toLocaleTimeString()}</span>
+ </div>;
+
+ return <tr key={s.id} className="row-small row-clickable">
+ <td><CopyLinkPopover text={s["id"].substring(0, 8)} value={s["id"]}/></td>
+ <td>
+ <LinkPopover text={dateTimeToTime(s["started_at"])} content={timeInfo} placement="right"/>
+ </td>
<td>{durationBetween(s["started_at"], s["completed_at"])}</td>
<td>{formatSize(s["size"])}</td>
<td>{s["processed_packets"]}</td>
@@ -143,8 +157,8 @@ class PcapsPane extends Component {
placement="left"/></td>
<td className="table-cell-action"><a href={"/api/pcap/sessions/" + s["id"] + "/download"}>download</a>
</td>
- </tr>
- );
+ </tr>;
+ });
const handleUploadFileChange = (file) => {
this.setState({
diff --git a/frontend/src/components/panels/RulesPane.js b/frontend/src/components/panels/RulesPane.js
index a66cde7..d872b47 100644
--- a/frontend/src/components/panels/RulesPane.js
+++ b/frontend/src/components/panels/RulesPane.js
@@ -15,26 +15,26 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-import React, {Component} from 'react';
-import './common.scss';
-import './RulesPane.scss';
-import Table from "react-bootstrap/Table";
+import React, {Component} from "react";
import {Col, Container, Row} from "react-bootstrap";
-import InputField from "../fields/InputField";
-import CheckField from "../fields/CheckField";
-import TextField from "../fields/TextField";
+import Table from "react-bootstrap/Table";
import backend from "../../backend";
-import NumericField from "../fields/extensions/NumericField";
-import ColorField from "../fields/extensions/ColorField";
-import ChoiceField from "../fields/ChoiceField";
-import ButtonField from "../fields/ButtonField";
+import dispatcher from "../../dispatcher";
import validation from "../../validation";
+import ButtonField from "../fields/ButtonField";
+import CheckField from "../fields/CheckField";
+import ChoiceField from "../fields/ChoiceField";
+import ColorField from "../fields/extensions/ColorField";
+import NumericField from "../fields/extensions/NumericField";
+import InputField from "../fields/InputField";
+import TextField from "../fields/TextField";
+import CopyLinkPopover from "../objects/CopyLinkPopover";
import LinkPopover from "../objects/LinkPopover";
-import {randomClassName} from "../../utils";
-import dispatcher from "../../dispatcher";
+import "./common.scss";
+import "./RulesPane.scss";
-const classNames = require('classnames');
-const _ = require('lodash');
+const classNames = require("classnames");
+const _ = require("lodash");
class RulesPane extends Component {
@@ -88,15 +88,20 @@ class RulesPane extends Component {
this.reset();
this.loadRules();
- dispatcher.register("notifications", payload => {
- if (payload.event === "rules.new" || payload.event === "rules.edit") {
- this.loadRules();
- }
- });
-
+ dispatcher.register("notifications", this.handleNotifications);
document.title = "caronte:~/rules$";
}
+ componentWillUnmount() {
+ dispatcher.unregister(this.handleNotifications);
+ }
+
+ handleNotifications = (payload) => {
+ if (payload.event === "rules.new" || payload.event === "rules.edit") {
+ this.loadRules();
+ }
+ };
+
loadRules = () => {
backend.get("/api/rules").then(res => this.setState({rules: res.json, rulesStatusCode: res.status}))
.catch(res => this.setState({rulesStatusCode: res.status, rulesResponse: JSON.stringify(res.json)}));
@@ -249,7 +254,7 @@ class RulesPane extends Component {
this.reset();
this.setState({selectedRule: _.cloneDeep(r)});
}} className={classNames("row-small", "row-clickable", {"row-selected": rule.id === r.id})}>
- <td>{r["id"].substring(0, 8)}</td>
+ <CopyLinkPopover text={r["id"].substring(0, 8)} value={r["id"]}/>
<td>{r["name"]}</td>
<td><ButtonField name={r["color"]} color={r["color"]} small/></td>
<td>{r["notes"]}</td>
@@ -260,7 +265,7 @@ class RulesPane extends Component {
rule.patterns.concat(this.state.newPattern) :
rule.patterns
).map(p => p === pattern ?
- <tr key={randomClassName()}>
+ <tr key={"new_pattern"}>
<td style={{"width": "500px"}}>
<InputField small active={this.state.patternRegexFocused} value={pattern.regex}
onChange={(v) => {
diff --git a/frontend/src/components/panels/SearchPane.js b/frontend/src/components/panels/SearchPane.js
index d3c0c8b..d36e85e 100644
--- a/frontend/src/components/panels/SearchPane.js
+++ b/frontend/src/components/panels/SearchPane.js
@@ -60,15 +60,14 @@ class SearchPane extends Component {
this.reset();
this.loadSearches();
- dispatcher.register("notifications", payload => {
- if (payload.event === "searches.new") {
- this.loadSearches();
- }
- });
-
+ dispatcher.register("notifications", this.handleNotification);
document.title = "caronte:~/searches$";
}
+ componentWillUnmount() {
+ dispatcher.unregister(this.handleNotification);
+ }
+
loadSearches = () => {
backend.get("/api/searches")
.then(res => this.setState({searches: res.json, searchesStatusCode: res.status}))
@@ -77,14 +76,18 @@ class SearchPane extends Component {
performSearch = () => {
const options = this.state.currentSearchOptions;
+ this.setState({loading: true});
if (this.validateSearch(options)) {
backend.post("/api/searches/perform", options).then(res => {
this.reset();
- this.setState({searchStatusCode: res.status});
+ this.setState({searchStatusCode: res.status, loading: false});
this.loadSearches();
this.viewSearch(res.json.id);
}).catch(res => {
- this.setState({searchStatusCode: res.status, searchResponse: JSON.stringify(res.json)});
+ this.setState({
+ searchStatusCode: res.status, searchResponse: JSON.stringify(res.json),
+ loading: false
+ });
});
}
};
@@ -156,6 +159,12 @@ class SearchPane extends Component {
dispatcher.dispatch("connections_filters", {"performed_search": searchId});
};
+ handleNotification = (payload) => {
+ if (payload.event === "searches.new") {
+ this.loadSearches();
+ }
+ };
+
render() {
const options = this.state.currentSearchOptions;
@@ -263,7 +272,8 @@ class SearchPane extends Component {
onChange={v => this.updateParam(s => s["regex_search"]["not_pattern"] = v)}/>
<div className="checkbox-line">
- <CheckField checked={options["regex_search"]["case_insensitive"]} name="case_insensitive"
+ <CheckField checked={options["regex_search"]["case_insensitive"]}
+ name="case_insensitive"
readonly={textOptionsModified} small
onChange={(v) => this.updateParam(s => s["regex_search"]["case_insensitive"] = v)}/>
<CheckField checked={options["regex_search"]["multi_line"]} name="multi_line"
@@ -284,8 +294,10 @@ class SearchPane extends Component {
</div>
<div className="section-footer">
- <ButtonField variant="red" name="cancel" bordered onClick={this.reset}/>
- <ButtonField variant="green" name="perform_search" bordered onClick={this.performSearch}/>
+ <ButtonField variant="red" name="cancel" bordered disabled={this.state.loading}
+ onClick={this.reset}/>
+ <ButtonField variant="green" name="perform_search" bordered
+ disabled={this.state.loading} onClick={this.performSearch}/>
</div>
</div>
</div>
diff --git a/frontend/src/components/panels/ServicesPane.js b/frontend/src/components/panels/ServicesPane.js
index bc82356..48d9e29 100644
--- a/frontend/src/components/panels/ServicesPane.js
+++ b/frontend/src/components/panels/ServicesPane.js
@@ -15,24 +15,24 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-import React, {Component} from 'react';
-import './common.scss';
-import './ServicesPane.scss';
-import Table from "react-bootstrap/Table";
+import React, {Component} from "react";
import {Col, Container, Row} from "react-bootstrap";
-import InputField from "../fields/InputField";
-import TextField from "../fields/TextField";
+import Table from "react-bootstrap/Table";
import backend from "../../backend";
-import NumericField from "../fields/extensions/NumericField";
-import ColorField from "../fields/extensions/ColorField";
-import ButtonField from "../fields/ButtonField";
+import dispatcher from "../../dispatcher";
+import {createCurlCommand} from "../../utils";
import validation from "../../validation";
+import ButtonField from "../fields/ButtonField";
+import ColorField from "../fields/extensions/ColorField";
+import NumericField from "../fields/extensions/NumericField";
+import InputField from "../fields/InputField";
+import TextField from "../fields/TextField";
import LinkPopover from "../objects/LinkPopover";
-import {createCurlCommand} from "../../utils";
-import dispatcher from "../../dispatcher";
+import "./common.scss";
+import "./ServicesPane.scss";
-const classNames = require('classnames');
-const _ = require('lodash');
+const classNames = require("classnames");
+const _ = require("lodash");
class ServicesPane extends Component {
@@ -52,15 +52,20 @@ class ServicesPane extends Component {
this.reset();
this.loadServices();
- dispatcher.register("notifications", payload => {
- if (payload.event === "services.edit") {
- this.loadServices();
- }
- });
-
+ dispatcher.register("notifications", this.handleNotifications);
document.title = "caronte:~/services$";
}
+ componentWillUnmount() {
+ dispatcher.unregister(this.handleNotifications);
+ }
+
+ handleNotifications = (payload) => {
+ if (payload.event === "services.edit") {
+ this.loadServices();
+ }
+ };
+
loadServices = () => {
backend.get("/api/services")
.then(res => this.setState({services: Object.values(res.json), servicesStatusCode: res.status}))
@@ -125,10 +130,10 @@ class ServicesPane extends Component {
<tr key={s.port} onClick={() => {
this.reset();
this.setState({isUpdate: true, currentService: _.cloneDeep(s)});
- }} className={classNames("row-small", "row-clickable", {"row-selected": service.port === s.port })}>
+ }} className={classNames("row-small", "row-clickable", {"row-selected": service.port === s.port})}>
<td>{s["port"]}</td>
<td>{s["name"]}</td>
- <td><ButtonField name={s["color"]} color={s["color"]} small /></td>
+ <td><ButtonField name={s["color"]} color={s["color"]} small/></td>
<td>{s["notes"]}</td>
</tr>
);
@@ -141,9 +146,9 @@ class ServicesPane extends Component {
<div className="section-header">
<span className="api-request">GET /api/services</span>
{this.state.servicesStatusCode &&
- <span className="api-response"><LinkPopover text={this.state.servicesStatusCode}
- content={this.state.servicesResponse}
- placement="left" /></span>}
+ <span className="api-response"><LinkPopover text={this.state.servicesStatusCode}
+ content={this.state.servicesResponse}
+ placement="left"/></span>}
</div>
<div className="section-content">
@@ -170,7 +175,7 @@ class ServicesPane extends Component {
<span className="api-request">PUT /api/services</span>
<span className="api-response"><LinkPopover text={this.state.serviceStatusCode}
content={this.state.serviceResponse}
- placement="left" /></span>
+ placement="left"/></span>
</div>
<div className="section-content">
@@ -179,17 +184,17 @@ class ServicesPane extends Component {
<Col>
<NumericField name="port" value={service.port}
onChange={(v) => this.updateParam((s) => s.port = v)}
- min={0} max={65565} error={this.state.servicePortError} />
+ min={0} max={65565} error={this.state.servicePortError}/>
<InputField name="name" value={service.name}
onChange={(v) => this.updateParam((s) => s.name = v)}
- error={this.state.serviceNameError} />
+ error={this.state.serviceNameError}/>
<ColorField value={service.color} error={this.state.serviceColorError}
- onChange={(v) => this.updateParam((s) => s.color = v)} />
+ onChange={(v) => this.updateParam((s) => s.color = v)}/>
</Col>
<Col>
<TextField name="notes" rows={7} value={service.notes}
- onChange={(v) => this.updateParam((s) => s.notes = v)} />
+ onChange={(v) => this.updateParam((s) => s.notes = v)}/>
</Col>
</Row>
</Container>
@@ -199,8 +204,9 @@ class ServicesPane extends Component {
<div className="section-footer">
{<ButtonField variant="red" name="cancel" bordered onClick={this.reset}/>}
- <ButtonField variant={isUpdate ? "blue" : "green"} name={isUpdate ? "update_service" : "add_service"}
- bordered onClick={this.updateService} />
+ <ButtonField variant={isUpdate ? "blue" : "green"}
+ name={isUpdate ? "update_service" : "add_service"}
+ bordered onClick={this.updateService}/>
</div>
</div>
</div>