From d4bac2d6741f7a291522c29c9ecc87c3e32e21d4 Mon Sep 17 00:00:00 2001
From: Emiliano Ciavatta
Date: Fri, 16 Oct 2020 14:16:44 +0200
Subject: Add notification when pcap have been processed
---
frontend/src/components/App.js | 6 +-
frontend/src/components/Notifications.js | 17 +++-
frontend/src/components/Timeline.js | 88 +++++++++--------
frontend/src/components/fields/ButtonField.js | 2 +-
frontend/src/components/fields/ChoiceField.js | 10 +-
frontend/src/components/objects/Connection.js | 36 ++-----
.../components/objects/ConnectionMatchedRules.js | 10 +-
frontend/src/components/objects/CopyLinkPopover.js | 54 +++++++++++
frontend/src/components/objects/MessageAction.js | 15 +--
frontend/src/components/panels/ConnectionsPane.js | 107 +++++++++++----------
frontend/src/components/panels/PcapsPane.js | 40 +++++---
frontend/src/components/panels/RulesPane.js | 51 +++++-----
frontend/src/components/panels/SearchPane.js | 34 ++++---
frontend/src/components/panels/ServicesPane.js | 68 +++++++------
14 files changed, 316 insertions(+), 222 deletions(-)
create mode 100644 frontend/src/components/objects/CopyLinkPopover.js
(limited to 'frontend/src/components')
diff --git a/frontend/src/components/App.js b/frontend/src/components/App.js
index 0f700db..888ff86 100644
--- a/frontend/src/components/App.js
+++ b/frontend/src/components/App.js
@@ -15,10 +15,10 @@
* along with this program. If not, see .
*/
-import React, {Component} from 'react';
-import ConfigurationPage from "./pages/ConfigurationPage";
-import Notifications from "./Notifications";
+import React, {Component} from "react";
import dispatcher from "../dispatcher";
+import Notifications from "./Notifications";
+import ConfigurationPage from "./pages/ConfigurationPage";
import MainPage from "./pages/MainPage";
import ServiceUnavailablePage from "./pages/ServiceUnavailablePage";
diff --git a/frontend/src/components/Notifications.js b/frontend/src/components/Notifications.js
index 56a4508..92731d9 100644
--- a/frontend/src/components/Notifications.js
+++ b/frontend/src/components/Notifications.js
@@ -17,6 +17,7 @@
import React, {Component} from "react";
import dispatcher from "../dispatcher";
+import {randomClassName} from "../utils";
import "./Notifications.scss";
const _ = require("lodash");
@@ -30,9 +31,15 @@ class Notifications extends Component {
};
componentDidMount() {
- dispatcher.register("notifications", n => this.notificationHandler(n));
+ dispatcher.register("notifications", this.handleNotifications);
}
+ componentWillUnmount() {
+ dispatcher.unregister(this.handleNotifications);
+ }
+
+ handleNotifications = (n) => this.notificationHandler(n);
+
notificationHandler = (n) => {
switch (n.event) {
case "connected":
@@ -54,6 +61,11 @@ class Notifications extends Component {
n.description = `existing rule updated: ${n.message["name"]}`;
n.variant = "blue";
return this.pushNotification(n);
+ case "pcap.completed":
+ n.title = "new pcap analyzed";
+ n.description = `${n.message["processed_packets"]} packets processed`;
+ n.variant = "blue";
+ return this.pushNotification(n);
default:
return;
}
@@ -61,6 +73,7 @@ class Notifications extends Component {
pushNotification = (notification) => {
const notifications = this.state.notifications;
+ notification.id = randomClassName();
notifications.push(notification);
this.setState({notifications});
setTimeout(() => {
@@ -103,7 +116,7 @@ class Notifications extends Component {
if (n.variant) {
notificationClassnames[`notification-${n.variant}`] = true;
}
- return
+ return
{n.title}
{n.description}
;
diff --git a/frontend/src/components/Timeline.js b/frontend/src/components/Timeline.js
index 8d1fd40..94fa4d0 100644
--- a/frontend/src/components/Timeline.js
+++ b/frontend/src/components/Timeline.js
@@ -15,8 +15,9 @@
* along with this program. If not, see
.
*/
-import React, {Component} from 'react';
-import './Timeline.scss';
+import {TimeRange, TimeSeries} from "pondjs";
+import React, {Component} from "react";
+import {withRouter} from "react-router-dom";
import {
ChartContainer,
ChartRow,
@@ -27,15 +28,14 @@ import {
styler,
YAxis
} from "react-timeseries-charts";
-import {TimeRange, TimeSeries} from "pondjs";
import backend from "../backend";
-import ChoiceField from "./fields/ChoiceField";
-import {withRouter} from "react-router-dom";
-import log from "../log";
import dispatcher from "../dispatcher";
+import log from "../log";
+import ChoiceField from "./fields/ChoiceField";
+import "./Timeline.scss";
const minutes = 60 * 1000;
-const classNames = require('classnames');
+const classNames = require("classnames");
const leftSelectionPaddingMultiplier = 24;
const rightSelectionPaddingMultiplier = 8;
@@ -61,42 +61,17 @@ class Timeline extends Component {
});
this.loadStatistics(this.state.metric).then(() => log.debug("Statistics loaded after mount"));
-
- this.connectionsFiltersCallback = (payload) => {
- if ("service_port" in payload && this.state.servicePortFilter !== payload["service_port"]) {
- this.setState({servicePortFilter: payload["service_port"]});
- this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after service port changed"));
- }
- if ("matched_rules" in payload && this.state.matchedRulesFilter !== payload["matched_rules"]) {
- this.setState({matchedRulesFilter: payload["matched_rules"]});
- this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after matched rules changed"));
- }
- };
- dispatcher.register("connections_filters", this.connectionsFiltersCallback);
-
- dispatcher.register("connection_updates", (payload) => {
- this.setState({
- selection: new TimeRange(payload.from, payload.to),
- });
- this.adjustSelection();
- });
-
- dispatcher.register("notifications", (payload) => {
- if (payload.event === "services.edit" && this.state.metric !== "matched_rules") {
- this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after services updates"));
- } else if (payload.event.startsWith("rules") && this.state.metric === "matched_rules") {
- this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after rules updates"));
- }
- });
-
- dispatcher.register("pulse_timeline", (payload) => {
- this.setState({pulseTimeline: true});
- setTimeout(() => this.setState({pulseTimeline: false}), payload.duration);
- });
+ dispatcher.register("connections_filters", this.handleConnectionsFiltersCallback);
+ dispatcher.register("connection_updates", this.handleConnectionUpdates);
+ dispatcher.register("notifications", this.handleNotifications);
+ dispatcher.register("pulse_timeline", this.handlePulseTimeline);
}
componentWillUnmount() {
- dispatcher.unregister(this.connectionsFiltersCallback);
+ dispatcher.unregister(this.handleConnectionsFiltersCallback);
+ dispatcher.unregister(this.handleConnectionUpdates);
+ dispatcher.unregister(this.handleNotifications);
+ dispatcher.unregister(this.handlePulseTimeline);
}
loadStatistics = async (metric) => {
@@ -217,6 +192,39 @@ class Timeline extends Component {
}, 1000);
};
+ handleConnectionsFiltersCallback = (payload) => {
+ if ("service_port" in payload && this.state.servicePortFilter !== payload["service_port"]) {
+ this.setState({servicePortFilter: payload["service_port"]});
+ this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after service port changed"));
+ }
+ if ("matched_rules" in payload && this.state.matchedRulesFilter !== payload["matched_rules"]) {
+ this.setState({matchedRulesFilter: payload["matched_rules"]});
+ this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after matched rules changed"));
+ }
+ };
+
+ handleConnectionUpdates = (payload) => {
+ this.setState({
+ selection: new TimeRange(payload.from, payload.to),
+ });
+ this.adjustSelection();
+ };
+
+ handleNotifications = (payload) => {
+ if (payload.event === "services.edit" && this.state.metric !== "matched_rules") {
+ this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after services updates"));
+ } else if (payload.event.startsWith("rules") && this.state.metric === "matched_rules") {
+ this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after rules updates"));
+ } else if (payload.event === "pcap.completed") {
+ this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after pcap processed"));
+ }
+ };
+
+ handlePulseTimeline = (payload) => {
+ this.setState({pulseTimeline: true});
+ setTimeout(() => this.setState({pulseTimeline: false}), payload.duration);
+ };
+
adjustSelection = () => {
const seriesRange = this.state.series.range();
const selection = this.state.selection;
diff --git a/frontend/src/components/fields/ButtonField.js b/frontend/src/components/fields/ButtonField.js
index de747a5..15ef179 100644
--- a/frontend/src/components/fields/ButtonField.js
+++ b/frontend/src/components/fields/ButtonField.js
@@ -58,7 +58,7 @@ class ButtonField extends Component {
{this.props.name}
+ onClick={handler} style={buttonStyle} disabled={this.props.disabled}>{this.props.name}
);
}
diff --git a/frontend/src/components/fields/ChoiceField.js b/frontend/src/components/fields/ChoiceField.js
index 14071c3..7e97d89 100644
--- a/frontend/src/components/fields/ChoiceField.js
+++ b/frontend/src/components/fields/ChoiceField.js
@@ -15,12 +15,12 @@
* along with this program. If not, see
.
*/
-import React, {Component} from 'react';
-import './ChoiceField.scss';
-import './common.scss';
+import React, {Component} from "react";
import {randomClassName} from "../../utils";
+import "./ChoiceField.scss";
+import "./common.scss";
-const classNames = require('classnames');
+const classNames = require("classnames");
class ChoiceField extends Component {
@@ -67,7 +67,7 @@ class ChoiceField extends Component {
}
return (
-
{!inline && name &&
{name}: }
{
if (name === "hide") {
const enabled = !this.props.data.hidden;
backend.post(`/api/connections/${this.props.data.id}/${enabled ? "hide" : "show"}`)
@@ -57,13 +50,7 @@ class Connection extends Component {
this.setState({update: true});
});
}
- if (name === "copy") {
- this.copyTextarea.current.select();
- document.execCommand("copy");
- this.setState({copiedMessage: true});
- setTimeout(() => this.setState({copiedMessage: false}), 3000);
- }
- }
+ };
render() {
let conn = this.props.data;
@@ -88,12 +75,6 @@ class Connection extends Component {
{conn.comment &&
}
;
- const copyPopoverContent =
- {this.state.copiedMessage ? Copied! :
- Click to copy the connection id }
-
-
;
-
return (
0})}>
@@ -121,9 +102,8 @@ class Connection extends Component {
this.handleAction("comment")}>@}
content={commentPopoverContent} placement="right"/>
- this.handleAction("copy")}>#}
- content={copyPopoverContent} placement="right"/>
+
);
diff --git a/frontend/src/components/objects/ConnectionMatchedRules.js b/frontend/src/components/objects/ConnectionMatchedRules.js
index 92bde49..cfd1254 100644
--- a/frontend/src/components/objects/ConnectionMatchedRules.js
+++ b/frontend/src/components/objects/ConnectionMatchedRules.js
@@ -15,11 +15,11 @@
* along with this program. If not, see
.
*/
-import React, {Component} from 'react';
-import './ConnectionMatchedRules.scss';
-import ButtonField from "../fields/ButtonField";
-import dispatcher from "../../dispatcher";
+import React, {Component} from "react";
import {withRouter} from "react-router-dom";
+import dispatcher from "../../dispatcher";
+import ButtonField from "../fields/ButtonField";
+import "./ConnectionMatchedRules.scss";
class ConnectionMatchedRules extends Component {
@@ -28,7 +28,7 @@ class ConnectionMatchedRules extends Component {
const rules = params.getAll("matched_rules");
if (!rules.includes(id)) {
rules.push(id);
- dispatcher.dispatch("connections_filters",{"matched_rules": rules});
+ dispatcher.dispatch("connections_filters", {"matched_rules": rules});
}
};
diff --git a/frontend/src/components/objects/CopyLinkPopover.js b/frontend/src/components/objects/CopyLinkPopover.js
new file mode 100644
index 0000000..fa9266f
--- /dev/null
+++ b/frontend/src/components/objects/CopyLinkPopover.js
@@ -0,0 +1,54 @@
+/*
+ * This file is part of caronte (https://github.com/eciavatta/caronte).
+ * Copyright (c) 2020 Emiliano Ciavatta.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
.
+ */
+
+import React, {Component} from "react";
+import TextField from "../fields/TextField";
+import LinkPopover from "./LinkPopover";
+
+class CopyLinkPopover extends Component {
+
+ state = {};
+
+ constructor(props) {
+ super(props);
+
+ this.copyTextarea = React.createRef();
+ }
+
+ handleClick = () => {
+ this.copyTextarea.current.select();
+ document.execCommand("copy");
+ this.setState({copiedMessage: true});
+ setTimeout(() => this.setState({copiedMessage: false}), 3000);
+ };
+
+ render() {
+ const copyPopoverContent =
+ {this.state.copiedMessage ? Copied! :
+ Click to copy }
+
+
;
+
+ return (
+
{this.props.text}}
+ content={copyPopoverContent} placement="right"/>
+ );
+ }
+}
+
+export default CopyLinkPopover;
diff --git a/frontend/src/components/objects/MessageAction.js b/frontend/src/components/objects/MessageAction.js
index 2b46320..e0c96e8 100644
--- a/frontend/src/components/objects/MessageAction.js
+++ b/frontend/src/components/objects/MessageAction.js
@@ -15,11 +15,11 @@
* along with this program. If not, see .
*/
-import React, {Component} from 'react';
-import './MessageAction.scss';
+import React, {Component} from "react";
import {Modal} from "react-bootstrap";
-import TextField from "../fields/TextField";
import ButtonField from "../fields/ButtonField";
+import TextField from "../fields/TextField";
+import "./MessageAction.scss";
class MessageAction extends Component {
@@ -34,7 +34,7 @@ class MessageAction extends Component {
copyActionValue() {
this.actionValue.current.select();
- document.execCommand('copy');
+ document.execCommand("copy");
this.setState({copyButtonText: "copied!"});
setTimeout(() => this.setState({copyButtonText: "copy"}), 3000);
}
@@ -54,11 +54,12 @@ class MessageAction extends Component {
-
+
-
-
+
+
);
diff --git a/frontend/src/components/panels/ConnectionsPane.js b/frontend/src/components/panels/ConnectionsPane.js
index ea47059..23c6114 100644
--- a/frontend/src/components/panels/ConnectionsPane.js
+++ b/frontend/src/components/panels/ConnectionsPane.js
@@ -15,20 +15,20 @@
* along with this program. If not, see .
*/
-import React, {Component} from 'react';
-import './ConnectionsPane.scss';
-import Connection from "../objects/Connection";
-import Table from 'react-bootstrap/Table';
+import React, {Component} from "react";
+import Table from "react-bootstrap/Table";
+import {Redirect} from "react-router";
import {withRouter} from "react-router-dom";
import backend from "../../backend";
-import ConnectionMatchedRules from "../objects/ConnectionMatchedRules";
-import log from "../../log";
-import ButtonField from "../fields/ButtonField";
import dispatcher from "../../dispatcher";
-import {Redirect} from "react-router";
+import log from "../../log";
import {updateParams} from "../../utils";
+import ButtonField from "../fields/ButtonField";
+import Connection from "../objects/Connection";
+import ConnectionMatchedRules from "../objects/ConnectionMatchedRules";
+import "./ConnectionsPane.scss";
-const classNames = require('classnames');
+const classNames = require("classnames");
class ConnectionsPane extends Component {
@@ -67,55 +67,56 @@ class ConnectionsPane extends Component {
this.loadConnections(additionalParams, urlParams, true).then(() => log.debug("Connections loaded"));
- this.connectionsFiltersCallback = payload => {
- const newParams = updateParams(this.state.urlParams, payload);
- if (this.state.urlParams.toString() === newParams.toString()) {
- return;
- }
-
- log.debug("Update following url params:", payload);
- this.queryStringRedirect = true;
- this.setState({urlParams: newParams});
-
- this.loadConnections({limit: this.queryLimit}, newParams)
- .then(() => log.info("ConnectionsPane reloaded after query string update"));
- };
- dispatcher.register("connections_filters", this.connectionsFiltersCallback);
-
- this.timelineUpdatesCallback = payload => {
- this.connectionsListRef.current.scrollTop = 0;
- this.loadConnections({
- "started_after": Math.round(payload.from.getTime() / 1000),
- "started_before": Math.round(payload.to.getTime() / 1000),
- limit: this.maxConnections
- }).then(() => log.info(`Loading connections between ${payload.from} and ${payload.to}`));
- };
- dispatcher.register("timeline_updates", this.timelineUpdatesCallback);
-
- this.notificationsCallback = payload => {
- if (payload.event === "rules.new" || payload.event === "rules.edit") {
- this.loadRules().then(() => log.debug("Loaded connection rules after notification update"));
- }
- if (payload.event === "services.edit") {
- this.loadServices().then(() => log.debug("Services reloaded after notification update"));
- }
- };
- dispatcher.register("notifications", this.notificationsCallback);
-
- this.pulseConnectionsViewCallback = payload => {
- this.setState({pulseConnectionsView: true});
- setTimeout(() => this.setState({pulseConnectionsView: false}), payload.duration);
- };
- dispatcher.register("pulse_connections_view", this.pulseConnectionsViewCallback);
+ dispatcher.register("connections_filters", this.handleConnectionsFilters);
+ dispatcher.register("timeline_updates", this.handleTimelineUpdates);
+ dispatcher.register("notifications", this.handleNotifications);
+ dispatcher.register("pulse_connections_view", this.handlePulseConnectionsView);
}
componentWillUnmount() {
- dispatcher.unregister(this.timelineUpdatesCallback);
- dispatcher.unregister(this.notificationsCallback);
- dispatcher.unregister(this.pulseConnectionsViewCallback);
- dispatcher.unregister(this.connectionsFiltersCallback);
+ dispatcher.unregister(this.handleConnectionsFilters);
+ dispatcher.unregister(this.handleTimelineUpdates);
+ dispatcher.unregister(this.handleNotifications);
+ dispatcher.unregister(this.handlePulseConnectionsView);
}
+ handleConnectionsFilters = (payload) => {
+ const newParams = updateParams(this.state.urlParams, payload);
+ if (this.state.urlParams.toString() === newParams.toString()) {
+ return;
+ }
+
+ log.debug("Update following url params:", payload);
+ this.queryStringRedirect = true;
+ this.setState({urlParams: newParams});
+
+ this.loadConnections({limit: this.queryLimit}, newParams)
+ .then(() => log.info("ConnectionsPane reloaded after query string update"));
+ };
+
+ handleTimelineUpdates = (payload) => {
+ this.connectionsListRef.current.scrollTop = 0;
+ this.loadConnections({
+ "started_after": Math.round(payload.from.getTime() / 1000),
+ "started_before": Math.round(payload.to.getTime() / 1000),
+ limit: this.maxConnections
+ }).then(() => log.info(`Loading connections between ${payload.from} and ${payload.to}`));
+ };
+
+ handleNotifications = (payload) => {
+ if (payload.event === "rules.new" || payload.event === "rules.edit") {
+ this.loadRules().then(() => log.debug("Loaded connection rules after notification update"));
+ }
+ if (payload.event === "services.edit") {
+ this.loadServices().then(() => log.debug("Services reloaded after notification update"));
+ }
+ };
+
+ handlePulseConnectionsView = (payload) => {
+ this.setState({pulseConnectionsView: true});
+ setTimeout(() => this.setState({pulseConnectionsView: false}), payload.duration);
+ };
+
connectionSelected = (c) => {
this.connectionSelectedRedirect = true;
this.setState({selected: c.id});
diff --git a/frontend/src/components/panels/PcapsPane.js b/frontend/src/components/panels/PcapsPane.js
index fd3db75..64e7804 100644
--- a/frontend/src/components/panels/PcapsPane.js
+++ b/frontend/src/components/panels/PcapsPane.js
@@ -24,6 +24,7 @@ import ButtonField from "../fields/ButtonField";
import CheckField from "../fields/CheckField";
import InputField from "../fields/InputField";
import TextField from "../fields/TextField";
+import CopyLinkPopover from "../objects/CopyLinkPopover";
import LinkPopover from "../objects/LinkPopover";
import "./common.scss";
import "./PcapsPane.scss";
@@ -44,16 +45,20 @@ class PcapsPane extends Component {
componentDidMount() {
this.loadSessions();
-
- dispatcher.register("notifications", (payload) => {
- if (payload.event === "pcap.upload" || payload.event === "pcap.file") {
- this.loadSessions();
- }
- });
-
+ dispatcher.register("notifications", this.handleNotifications);
document.title = "caronte:~/pcaps$";
}
+ componentWillUnmount() {
+ dispatcher.unregister(this.handleNotifications);
+ }
+
+ handleNotifications = (payload) => {
+ if (payload.event.startsWith("pcap")) {
+ this.loadSessions();
+ }
+ };
+
loadSessions = () => {
backend.get("/api/pcap/sessions")
.then((res) => this.setState({sessions: res.json, sessionsStatusCode: res.status}))
@@ -130,10 +135,19 @@ class PcapsPane extends Component {
};
render() {
- let sessions = this.state.sessions.map((s) =>
-
- {s["id"].substring(0, 8)}
- {dateTimeToTime(s["started_at"])}
+ let sessions = this.state.sessions.map((s) => {
+ const startedAt = new Date(s["started_at"]);
+ const completedAt = new Date(s["completed_at"]);
+ let timeInfo =
+ Started at {startedAt.toLocaleDateString() + " " + startedAt.toLocaleTimeString()}
+ Completed at {completedAt.toLocaleDateString() + " " + completedAt.toLocaleTimeString()}
+
;
+
+ return
+
+
+
+
{durationBetween(s["started_at"], s["completed_at"])}
{formatSize(s["size"])}
{s["processed_packets"]}
@@ -143,8 +157,8 @@ class PcapsPane extends Component {
placement="left"/>
download
-
- );
+ ;
+ });
const handleUploadFileChange = (file) => {
this.setState({
diff --git a/frontend/src/components/panels/RulesPane.js b/frontend/src/components/panels/RulesPane.js
index a66cde7..d872b47 100644
--- a/frontend/src/components/panels/RulesPane.js
+++ b/frontend/src/components/panels/RulesPane.js
@@ -15,26 +15,26 @@
* along with this program. If not, see .
*/
-import React, {Component} from 'react';
-import './common.scss';
-import './RulesPane.scss';
-import Table from "react-bootstrap/Table";
+import React, {Component} from "react";
import {Col, Container, Row} from "react-bootstrap";
-import InputField from "../fields/InputField";
-import CheckField from "../fields/CheckField";
-import TextField from "../fields/TextField";
+import Table from "react-bootstrap/Table";
import backend from "../../backend";
-import NumericField from "../fields/extensions/NumericField";
-import ColorField from "../fields/extensions/ColorField";
-import ChoiceField from "../fields/ChoiceField";
-import ButtonField from "../fields/ButtonField";
+import dispatcher from "../../dispatcher";
import validation from "../../validation";
+import ButtonField from "../fields/ButtonField";
+import CheckField from "../fields/CheckField";
+import ChoiceField from "../fields/ChoiceField";
+import ColorField from "../fields/extensions/ColorField";
+import NumericField from "../fields/extensions/NumericField";
+import InputField from "../fields/InputField";
+import TextField from "../fields/TextField";
+import CopyLinkPopover from "../objects/CopyLinkPopover";
import LinkPopover from "../objects/LinkPopover";
-import {randomClassName} from "../../utils";
-import dispatcher from "../../dispatcher";
+import "./common.scss";
+import "./RulesPane.scss";
-const classNames = require('classnames');
-const _ = require('lodash');
+const classNames = require("classnames");
+const _ = require("lodash");
class RulesPane extends Component {
@@ -88,15 +88,20 @@ class RulesPane extends Component {
this.reset();
this.loadRules();
- dispatcher.register("notifications", payload => {
- if (payload.event === "rules.new" || payload.event === "rules.edit") {
- this.loadRules();
- }
- });
-
+ dispatcher.register("notifications", this.handleNotifications);
document.title = "caronte:~/rules$";
}
+ componentWillUnmount() {
+ dispatcher.unregister(this.handleNotifications);
+ }
+
+ handleNotifications = (payload) => {
+ if (payload.event === "rules.new" || payload.event === "rules.edit") {
+ this.loadRules();
+ }
+ };
+
loadRules = () => {
backend.get("/api/rules").then(res => this.setState({rules: res.json, rulesStatusCode: res.status}))
.catch(res => this.setState({rulesStatusCode: res.status, rulesResponse: JSON.stringify(res.json)}));
@@ -249,7 +254,7 @@ class RulesPane extends Component {
this.reset();
this.setState({selectedRule: _.cloneDeep(r)});
}} className={classNames("row-small", "row-clickable", {"row-selected": rule.id === r.id})}>
- {r["id"].substring(0, 8)}
+
{r["name"]}
{r["notes"]}
@@ -260,7 +265,7 @@ class RulesPane extends Component {
rule.patterns.concat(this.state.newPattern) :
rule.patterns
).map(p => p === pattern ?
-
+
{
diff --git a/frontend/src/components/panels/SearchPane.js b/frontend/src/components/panels/SearchPane.js
index d3c0c8b..d36e85e 100644
--- a/frontend/src/components/panels/SearchPane.js
+++ b/frontend/src/components/panels/SearchPane.js
@@ -60,15 +60,14 @@ class SearchPane extends Component {
this.reset();
this.loadSearches();
- dispatcher.register("notifications", payload => {
- if (payload.event === "searches.new") {
- this.loadSearches();
- }
- });
-
+ dispatcher.register("notifications", this.handleNotification);
document.title = "caronte:~/searches$";
}
+ componentWillUnmount() {
+ dispatcher.unregister(this.handleNotification);
+ }
+
loadSearches = () => {
backend.get("/api/searches")
.then(res => this.setState({searches: res.json, searchesStatusCode: res.status}))
@@ -77,14 +76,18 @@ class SearchPane extends Component {
performSearch = () => {
const options = this.state.currentSearchOptions;
+ this.setState({loading: true});
if (this.validateSearch(options)) {
backend.post("/api/searches/perform", options).then(res => {
this.reset();
- this.setState({searchStatusCode: res.status});
+ this.setState({searchStatusCode: res.status, loading: false});
this.loadSearches();
this.viewSearch(res.json.id);
}).catch(res => {
- this.setState({searchStatusCode: res.status, searchResponse: JSON.stringify(res.json)});
+ this.setState({
+ searchStatusCode: res.status, searchResponse: JSON.stringify(res.json),
+ loading: false
+ });
});
}
};
@@ -156,6 +159,12 @@ class SearchPane extends Component {
dispatcher.dispatch("connections_filters", {"performed_search": searchId});
};
+ handleNotification = (payload) => {
+ if (payload.event === "searches.new") {
+ this.loadSearches();
+ }
+ };
+
render() {
const options = this.state.currentSearchOptions;
@@ -263,7 +272,8 @@ class SearchPane extends Component {
onChange={v => this.updateParam(s => s["regex_search"]["not_pattern"] = v)}/>
-
this.updateParam(s => s["regex_search"]["case_insensitive"] = v)}/>
-
-
+
+
diff --git a/frontend/src/components/panels/ServicesPane.js b/frontend/src/components/panels/ServicesPane.js
index bc82356..48d9e29 100644
--- a/frontend/src/components/panels/ServicesPane.js
+++ b/frontend/src/components/panels/ServicesPane.js
@@ -15,24 +15,24 @@
* along with this program. If not, see .
*/
-import React, {Component} from 'react';
-import './common.scss';
-import './ServicesPane.scss';
-import Table from "react-bootstrap/Table";
+import React, {Component} from "react";
import {Col, Container, Row} from "react-bootstrap";
-import InputField from "../fields/InputField";
-import TextField from "../fields/TextField";
+import Table from "react-bootstrap/Table";
import backend from "../../backend";
-import NumericField from "../fields/extensions/NumericField";
-import ColorField from "../fields/extensions/ColorField";
-import ButtonField from "../fields/ButtonField";
+import dispatcher from "../../dispatcher";
+import {createCurlCommand} from "../../utils";
import validation from "../../validation";
+import ButtonField from "../fields/ButtonField";
+import ColorField from "../fields/extensions/ColorField";
+import NumericField from "../fields/extensions/NumericField";
+import InputField from "../fields/InputField";
+import TextField from "../fields/TextField";
import LinkPopover from "../objects/LinkPopover";
-import {createCurlCommand} from "../../utils";
-import dispatcher from "../../dispatcher";
+import "./common.scss";
+import "./ServicesPane.scss";
-const classNames = require('classnames');
-const _ = require('lodash');
+const classNames = require("classnames");
+const _ = require("lodash");
class ServicesPane extends Component {
@@ -52,15 +52,20 @@ class ServicesPane extends Component {
this.reset();
this.loadServices();
- dispatcher.register("notifications", payload => {
- if (payload.event === "services.edit") {
- this.loadServices();
- }
- });
-
+ dispatcher.register("notifications", this.handleNotifications);
document.title = "caronte:~/services$";
}
+ componentWillUnmount() {
+ dispatcher.unregister(this.handleNotifications);
+ }
+
+ handleNotifications = (payload) => {
+ if (payload.event === "services.edit") {
+ this.loadServices();
+ }
+ };
+
loadServices = () => {
backend.get("/api/services")
.then(res => this.setState({services: Object.values(res.json), servicesStatusCode: res.status}))
@@ -125,10 +130,10 @@ class ServicesPane extends Component {
{
this.reset();
this.setState({isUpdate: true, currentService: _.cloneDeep(s)});
- }} className={classNames("row-small", "row-clickable", {"row-selected": service.port === s.port })}>
+ }} className={classNames("row-small", "row-clickable", {"row-selected": service.port === s.port})}>
{s["port"]}
{s["name"]}
-
+
{s["notes"]}
);
@@ -141,9 +146,9 @@ class ServicesPane extends Component {
GET /api/services
{this.state.servicesStatusCode &&
- }
+ }
@@ -170,7 +175,7 @@ class ServicesPane extends Component {
PUT /api/services
+ placement="left"/>
@@ -179,17 +184,17 @@ class ServicesPane extends Component {
this.updateParam((s) => s.port = v)}
- min={0} max={65565} error={this.state.servicePortError} />
+ min={0} max={65565} error={this.state.servicePortError}/>
this.updateParam((s) => s.name = v)}
- error={this.state.serviceNameError} />
+ error={this.state.serviceNameError}/>
this.updateParam((s) => s.color = v)} />
+ onChange={(v) => this.updateParam((s) => s.color = v)}/>
this.updateParam((s) => s.notes = v)} />
+ onChange={(v) => this.updateParam((s) => s.notes = v)}/>
@@ -199,8 +204,9 @@ class ServicesPane extends Component {
{}
-
+
--
cgit v1.2.3-70-g09d2