- {
- !this.loading &&
- this.setState({selectedConnection: c})}
- initialConnection={this.state.selectedConnection}/>
- }
-
-
- }/>
- }/>
- }/>
- }/>
- }/>
-
+
);
}
+
}
-export default withRouter(MainPane);
+export default MainPane;
diff --git a/frontend/src/components/panels/MainPane.scss b/frontend/src/components/panels/MainPane.scss
index 2973c00..c8460f2 100644
--- a/frontend/src/components/panels/MainPane.scss
+++ b/frontend/src/components/panels/MainPane.scss
@@ -1,22 +1,5 @@
@import "../../colors";
.main-pane {
- display: flex;
- height: 100%;
- padding: 0 15px;
- background-color: $color-primary-2;
- .pane {
- flex: 1;
- }
-
- .connections-pane {
- flex: 1 0;
- margin-right: 7.5px;
- }
-
- .details-pane {
- flex: 1 1;
- margin-left: 7.5px;
- }
}
diff --git a/frontend/src/components/panels/PcapPane.js b/frontend/src/components/panels/PcapPane.js
deleted file mode 100644
index d5c2225..0000000
--- a/frontend/src/components/panels/PcapPane.js
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * This file is part of caronte (https://github.com/eciavatta/caronte).
- * Copyright (c) 2020 Emiliano Ciavatta.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
.
- */
-
-import React, {Component} from 'react';
-import './PcapPane.scss';
-import './common.scss';
-import Table from "react-bootstrap/Table";
-import backend from "../../backend";
-import {createCurlCommand, dateTimeToTime, durationBetween, formatSize} from "../../utils";
-import InputField from "../fields/InputField";
-import CheckField from "../fields/CheckField";
-import TextField from "../fields/TextField";
-import ButtonField from "../fields/ButtonField";
-import LinkPopover from "../objects/LinkPopover";
-import dispatcher from "../../dispatcher";
-
-class PcapPane extends Component {
-
- state = {
- sessions: [],
- isUploadFileValid: true,
- isUploadFileFocused: false,
- uploadFlushAll: false,
- isFileValid: true,
- isFileFocused: false,
- fileValue: "",
- processFlushAll: false,
- deleteOriginalFile: false
- };
-
- componentDidMount() {
- this.loadSessions();
-
- dispatcher.register("notifications", payload => {
- if (payload.event === "pcap.upload" || payload.event === "pcap.file") {
- this.loadSessions();
- }
- });
-
- document.title = "caronte:~/pcaps$";
- }
-
- loadSessions = () => {
- backend.get("/api/pcap/sessions")
- .then(res => this.setState({sessions: res.json, sessionsStatusCode: res.status}))
- .catch(res => this.setState({
- sessions: res.json, sessionsStatusCode: res.status,
- sessionsResponse: JSON.stringify(res.json)
- }));
- };
-
- uploadPcap = () => {
- if (this.state.uploadSelectedFile == null || !this.state.isUploadFileValid) {
- this.setState({isUploadFileFocused: true});
- return;
- }
-
- const formData = new FormData();
- formData.append("file", this.state.uploadSelectedFile);
- formData.append("flush_all", this.state.uploadFlushAll);
- backend.postFile("/api/pcap/upload", formData).then(res => {
- this.setState({
- uploadStatusCode: res.status,
- uploadResponse: JSON.stringify(res.json)
- });
- this.resetUpload();
- this.loadSessions();
- }).catch(res => this.setState({
- uploadStatusCode: res.status,
- uploadResponse: JSON.stringify(res.json)
- })
- );
- };
-
- processPcap = () => {
- if (this.state.fileValue === "" || !this.state.isFileValid) {
- this.setState({isFileFocused: true});
- return;
- }
-
- backend.post("/api/pcap/file", {
- file: this.state.fileValue,
- flush_all: this.state.processFlushAll,
- delete_original_file: this.state.deleteOriginalFile
- }).then(res => {
- this.setState({
- processStatusCode: res.status,
- processResponse: JSON.stringify(res.json)
- });
- this.resetProcess();
- this.loadSessions();
- }).catch(res => this.setState({
- processStatusCode: res.status,
- processResponse: JSON.stringify(res.json)
- })
- );
- };
-
- resetUpload = () => {
- this.setState({
- isUploadFileValid: true,
- isUploadFileFocused: false,
- uploadFlushAll: false,
- uploadSelectedFile: null
- });
- };
-
- resetProcess = () => {
- this.setState({
- isFileValid: true,
- isFileFocused: false,
- fileValue: "",
- processFlushAll: false,
- deleteOriginalFile: false,
- });
- };
-
- render() {
- let sessions = this.state.sessions.map(s =>
-
- {s["id"].substring(0, 8)}
- {dateTimeToTime(s["started_at"])}
- {durationBetween(s["started_at"], s["completed_at"])}
- {formatSize(s["size"])}
- {s["processed_packets"]}
- {s["invalid_packets"]}
-
- download
-
-
- );
-
- const handleUploadFileChange = (file) => {
- this.setState({
- isUploadFileValid: file == null || (file.type.endsWith("pcap") || file.type.endsWith("pcapng")),
- isUploadFileFocused: false,
- uploadSelectedFile: file,
- uploadStatusCode: null,
- uploadResponse: null
- });
- };
-
- const handleFileChange = (file) => {
- this.setState({
- isFileValid: (file.endsWith("pcap") || file.endsWith("pcapng")),
- isFileFocused: false,
- fileValue: file,
- processStatusCode: null,
- processResponse: null
- });
- };
-
- const uploadCurlCommand = createCurlCommand("pcap/upload", "POST", null, {
- file: "@" + ((this.state.uploadSelectedFile != null && this.state.isUploadFileValid) ?
- this.state.uploadSelectedFile.name : "invalid.pcap"),
- flush_all: this.state.uploadFlushAll
- });
-
- const fileCurlCommand = createCurlCommand("pcap/file", "POST", {
- file: this.state.fileValue,
- flush_all: this.state.processFlushAll,
- delete_original_file: this.state.deleteOriginalFile
- });
-
- return (
-
-
-
- GET /api/pcap/sessions
-
-
-
-
-
-
-
-
- id
- started_at
- duration
- size
- processed_packets
- invalid_packets
- packets_per_service
- actions
-
-
-
- {sessions}
-
-
-
-
-
-
-
-
-
- POST /api/pcap/upload
-
-
-
-
-
-
-
- options:
- this.setState({uploadFlushAll: v})}/>
-
-
-
-
-
-
-
-
-
-
- POST /api/pcap/file
-
-
-
-
-
-
-
-
- this.setState({processFlushAll: v})}/>
- this.setState({deleteOriginalFile: v})}/>
-
-
-
-
-
-
-
-
-
- );
- }
-}
-
-export default PcapPane;
diff --git a/frontend/src/components/panels/PcapPane.scss b/frontend/src/components/panels/PcapPane.scss
deleted file mode 100644
index 4dbc2b2..0000000
--- a/frontend/src/components/panels/PcapPane.scss
+++ /dev/null
@@ -1,38 +0,0 @@
-@import "../../colors.scss";
-
-.pcap-pane {
- display: flex;
- flex-direction: column;
-
- .pcap-list {
- overflow: hidden;
- flex: 1;
-
- .section-content {
- height: 100%;
- }
-
- .section-table {
- height: calc(100% - 30px);
- }
-
- .table-cell-action {
- font-size: 13px;
- font-weight: 600;
- }
- }
-
- .upload-actions {
- display: flex;
- align-items: flex-end;
- margin-bottom: 20px;
- }
-
- .upload-options {
- flex: 1;
-
- span {
- font-size: 0.9em;
- }
- }
-}
diff --git a/frontend/src/components/panels/PcapsPane.js b/frontend/src/components/panels/PcapsPane.js
new file mode 100644
index 0000000..8722230
--- /dev/null
+++ b/frontend/src/components/panels/PcapsPane.js
@@ -0,0 +1,273 @@
+/*
+ * This file is part of caronte (https://github.com/eciavatta/caronte).
+ * Copyright (c) 2020 Emiliano Ciavatta.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
.
+ */
+
+import React, {Component} from 'react';
+import './PcapsPane.scss';
+import './common.scss';
+import Table from "react-bootstrap/Table";
+import backend from "../../backend";
+import {createCurlCommand, dateTimeToTime, durationBetween, formatSize} from "../../utils";
+import InputField from "../fields/InputField";
+import CheckField from "../fields/CheckField";
+import TextField from "../fields/TextField";
+import ButtonField from "../fields/ButtonField";
+import LinkPopover from "../objects/LinkPopover";
+import dispatcher from "../../dispatcher";
+
+class PcapsPane extends Component {
+
+ state = {
+ sessions: [],
+ isUploadFileValid: true,
+ isUploadFileFocused: false,
+ uploadFlushAll: false,
+ isFileValid: true,
+ isFileFocused: false,
+ fileValue: "",
+ processFlushAll: false,
+ deleteOriginalFile: false
+ };
+
+ componentDidMount() {
+ this.loadSessions();
+
+ dispatcher.register("notifications", payload => {
+ if (payload.event === "pcap.upload" || payload.event === "pcap.file") {
+ this.loadSessions();
+ }
+ });
+
+ document.title = "caronte:~/pcaps$";
+ }
+
+ loadSessions = () => {
+ backend.get("/api/pcap/sessions")
+ .then(res => this.setState({sessions: res.json, sessionsStatusCode: res.status}))
+ .catch(res => this.setState({
+ sessions: res.json, sessionsStatusCode: res.status,
+ sessionsResponse: JSON.stringify(res.json)
+ }));
+ };
+
+ uploadPcap = () => {
+ if (this.state.uploadSelectedFile == null || !this.state.isUploadFileValid) {
+ this.setState({isUploadFileFocused: true});
+ return;
+ }
+
+ const formData = new FormData();
+ formData.append("file", this.state.uploadSelectedFile);
+ formData.append("flush_all", this.state.uploadFlushAll);
+ backend.postFile("/api/pcap/upload", formData).then(res => {
+ this.setState({
+ uploadStatusCode: res.status,
+ uploadResponse: JSON.stringify(res.json)
+ });
+ this.resetUpload();
+ this.loadSessions();
+ }).catch(res => this.setState({
+ uploadStatusCode: res.status,
+ uploadResponse: JSON.stringify(res.json)
+ })
+ );
+ };
+
+ processPcap = () => {
+ if (this.state.fileValue === "" || !this.state.isFileValid) {
+ this.setState({isFileFocused: true});
+ return;
+ }
+
+ backend.post("/api/pcap/file", {
+ file: this.state.fileValue,
+ flush_all: this.state.processFlushAll,
+ delete_original_file: this.state.deleteOriginalFile
+ }).then(res => {
+ this.setState({
+ processStatusCode: res.status,
+ processResponse: JSON.stringify(res.json)
+ });
+ this.resetProcess();
+ this.loadSessions();
+ }).catch(res => this.setState({
+ processStatusCode: res.status,
+ processResponse: JSON.stringify(res.json)
+ })
+ );
+ };
+
+ resetUpload = () => {
+ this.setState({
+ isUploadFileValid: true,
+ isUploadFileFocused: false,
+ uploadFlushAll: false,
+ uploadSelectedFile: null
+ });
+ };
+
+ resetProcess = () => {
+ this.setState({
+ isFileValid: true,
+ isFileFocused: false,
+ fileValue: "",
+ processFlushAll: false,
+ deleteOriginalFile: false,
+ });
+ };
+
+ render() {
+ let sessions = this.state.sessions.map(s =>
+
+ {s["id"].substring(0, 8)}
+ {dateTimeToTime(s["started_at"])}
+ {durationBetween(s["started_at"], s["completed_at"])}
+ {formatSize(s["size"])}
+ {s["processed_packets"]}
+ {s["invalid_packets"]}
+
+ download
+
+
+ );
+
+ const handleUploadFileChange = (file) => {
+ this.setState({
+ isUploadFileValid: file == null || (file.type.endsWith("pcap") || file.type.endsWith("pcapng")),
+ isUploadFileFocused: false,
+ uploadSelectedFile: file,
+ uploadStatusCode: null,
+ uploadResponse: null
+ });
+ };
+
+ const handleFileChange = (file) => {
+ this.setState({
+ isFileValid: (file.endsWith("pcap") || file.endsWith("pcapng")),
+ isFileFocused: false,
+ fileValue: file,
+ processStatusCode: null,
+ processResponse: null
+ });
+ };
+
+ const uploadCurlCommand = createCurlCommand("pcap/upload", "POST", null, {
+ file: "@" + ((this.state.uploadSelectedFile != null && this.state.isUploadFileValid) ?
+ this.state.uploadSelectedFile.name : "invalid.pcap"),
+ flush_all: this.state.uploadFlushAll
+ });
+
+ const fileCurlCommand = createCurlCommand("pcap/file", "POST", {
+ file: this.state.fileValue,
+ flush_all: this.state.processFlushAll,
+ delete_original_file: this.state.deleteOriginalFile
+ });
+
+ return (
+
+
+
+ GET /api/pcap/sessions
+
+
+
+
+
+
+
+
+ id
+ started_at
+ duration
+ size
+ processed_packets
+ invalid_packets
+ packets_per_service
+ actions
+
+
+
+ {sessions}
+
+
+
+
+
+
+
+
+
+ POST /api/pcap/upload
+
+
+
+
+
+
+
+ options:
+ this.setState({uploadFlushAll: v})}/>
+
+
+
+
+
+
+
+
+
+
+ POST /api/pcap/file
+
+
+
+
+
+
+
+
+ this.setState({processFlushAll: v})}/>
+ this.setState({deleteOriginalFile: v})}/>
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default PcapsPane;
diff --git a/frontend/src/components/panels/PcapsPane.scss b/frontend/src/components/panels/PcapsPane.scss
new file mode 100644
index 0000000..4dbc2b2
--- /dev/null
+++ b/frontend/src/components/panels/PcapsPane.scss
@@ -0,0 +1,38 @@
+@import "../../colors.scss";
+
+.pcap-pane {
+ display: flex;
+ flex-direction: column;
+
+ .pcap-list {
+ overflow: hidden;
+ flex: 1;
+
+ .section-content {
+ height: 100%;
+ }
+
+ .section-table {
+ height: calc(100% - 30px);
+ }
+
+ .table-cell-action {
+ font-size: 13px;
+ font-weight: 600;
+ }
+ }
+
+ .upload-actions {
+ display: flex;
+ align-items: flex-end;
+ margin-bottom: 20px;
+ }
+
+ .upload-options {
+ flex: 1;
+
+ span {
+ font-size: 0.9em;
+ }
+ }
+}
diff --git a/frontend/src/components/panels/RulePane.js b/frontend/src/components/panels/RulePane.js
deleted file mode 100644
index 9913962..0000000
--- a/frontend/src/components/panels/RulePane.js
+++ /dev/null
@@ -1,438 +0,0 @@
-/*
- * This file is part of caronte (https://github.com/eciavatta/caronte).
- * Copyright (c) 2020 Emiliano Ciavatta.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
.
- */
-
-import React, {Component} from 'react';
-import './common.scss';
-import './RulePane.scss';
-import Table from "react-bootstrap/Table";
-import {Col, Container, Row} from "react-bootstrap";
-import InputField from "../fields/InputField";
-import CheckField from "../fields/CheckField";
-import TextField from "../fields/TextField";
-import backend from "../../backend";
-import NumericField from "../fields/extensions/NumericField";
-import ColorField from "../fields/extensions/ColorField";
-import ChoiceField from "../fields/ChoiceField";
-import ButtonField from "../fields/ButtonField";
-import validation from "../../validation";
-import LinkPopover from "../objects/LinkPopover";
-import {randomClassName} from "../../utils";
-import dispatcher from "../../dispatcher";
-
-const classNames = require('classnames');
-const _ = require('lodash');
-
-class RulePane extends Component {
-
- emptyRule = {
- "name": "",
- "color": "",
- "notes": "",
- "enabled": true,
- "patterns": [],
- "filter": {
- "service_port": 0,
- "client_address": "",
- "client_port": 0,
- "min_duration": 0,
- "max_duration": 0,
- "min_bytes": 0,
- "max_bytes": 0
- },
- "version": 0
- };
- emptyPattern = {
- "regex": "",
- "flags": {
- "caseless": false,
- "dot_all": false,
- "multi_line": false,
- "utf_8_mode": false,
- "unicode_property": false
- },
- "min_occurrences": 0,
- "max_occurrences": 0,
- "direction": 0
- };
- state = {
- rules: [],
- newRule: this.emptyRule,
- newPattern: this.emptyPattern
- };
-
- constructor(props) {
- super(props);
-
- this.directions = {
- 0: "both",
- 1: "c->s",
- 2: "s->c"
- };
- }
-
- componentDidMount() {
- this.reset();
- this.loadRules();
-
- dispatcher.register("notifications", payload => {
- if (payload.event === "rules.new" || payload.event === "rules.edit") {
- this.loadRules();
- }
- });
-
- document.title = "caronte:~/rules$";
- }
-
- loadRules = () => {
- backend.get("/api/rules").then(res => this.setState({rules: res.json, rulesStatusCode: res.status}))
- .catch(res => this.setState({rulesStatusCode: res.status, rulesResponse: JSON.stringify(res.json)}));
- };
-
- addRule = () => {
- if (this.validateRule(this.state.newRule)) {
- backend.post("/api/rules", this.state.newRule).then(res => {
- this.reset();
- this.setState({ruleStatusCode: res.status});
- this.loadRules();
- }).catch(res => {
- this.setState({ruleStatusCode: res.status, ruleResponse: JSON.stringify(res.json)});
- });
- }
- };
-
- updateRule = () => {
- const rule = this.state.selectedRule;
- if (this.validateRule(rule)) {
- backend.put(`/api/rules/${rule.id}`, rule).then(res => {
- this.reset();
- this.setState({ruleStatusCode: res.status});
- this.loadRules();
- }).catch(res => {
- this.setState({ruleStatusCode: res.status, ruleResponse: JSON.stringify(res.json)});
- });
- }
- };
-
- validateRule = (rule) => {
- let valid = true;
- if (rule.name.length < 3) {
- this.setState({ruleNameError: "name.length < 3"});
- valid = false;
- }
- if (!validation.isValidColor(rule.color)) {
- this.setState({ruleColorError: "color is not hexcolor"});
- valid = false;
- }
- if (!validation.isValidPort(rule.filter.service_port)) {
- this.setState({ruleServicePortError: "service_port > 65565"});
- valid = false;
- }
- if (!validation.isValidPort(rule.filter.client_port)) {
- this.setState({ruleClientPortError: "client_port > 65565"});
- valid = false;
- }
- if (!validation.isValidAddress(rule.filter.client_address)) {
- this.setState({ruleClientAddressError: "client_address is not ip_address"});
- valid = false;
- }
- if (rule.filter.min_duration > rule.filter.max_duration) {
- this.setState({ruleDurationError: "min_duration > max_dur."});
- valid = false;
- }
- if (rule.filter.min_bytes > rule.filter.max_bytes) {
- this.setState({ruleBytesError: "min_bytes > max_bytes"});
- valid = false;
- }
- if (rule.patterns.length < 1) {
- this.setState({rulePatternsError: "patterns.length < 1"});
- valid = false;
- }
-
- return valid;
- };
-
- reset = () => {
- const newRule = _.cloneDeep(this.emptyRule);
- const newPattern = _.cloneDeep(this.emptyPattern);
- this.setState({
- selectedRule: null,
- newRule: newRule,
- selectedPattern: null,
- newPattern: newPattern,
- patternRegexFocused: false,
- patternOccurrencesFocused: false,
- ruleNameError: null,
- ruleColorError: null,
- ruleServicePortError: null,
- ruleClientPortError: null,
- ruleClientAddressError: null,
- ruleDurationError: null,
- ruleBytesError: null,
- rulePatternsError: null,
- ruleStatusCode: null,
- rulesStatusCode: null,
- ruleResponse: null,
- rulesResponse: null
- });
- };
-
- updateParam = (callback) => {
- const updatedRule = this.currentRule();
- callback(updatedRule);
- this.setState({newRule: updatedRule});
- };
-
- currentRule = () => this.state.selectedRule != null ? this.state.selectedRule : this.state.newRule;
-
- addPattern = (pattern) => {
- if (!this.validatePattern(pattern)) {
- return;
- }
-
- const newPattern = _.cloneDeep(this.emptyPattern);
- this.currentRule().patterns.push(pattern);
- this.setState({
- newPattern: newPattern
- });
- };
-
- editPattern = (pattern) => {
- this.setState({
- selectedPattern: pattern
- });
- };
-
- updatePattern = (pattern) => {
- if (!this.validatePattern(pattern)) {
- return;
- }
-
- this.setState({
- selectedPattern: null
- });
- };
-
- validatePattern = (pattern) => {
- let valid = true;
- if (pattern.regex === "") {
- valid = false;
- this.setState({patternRegexFocused: true});
- }
- if (pattern.min_occurrences > pattern.max_occurrences) {
- valid = false;
- this.setState({patternOccurrencesFocused: true});
- }
- return valid;
- };
-
- render() {
- const isUpdate = this.state.selectedRule != null;
- const rule = this.currentRule();
- const pattern = this.state.selectedPattern || this.state.newPattern;
-
- let rules = this.state.rules.map(r =>
-
{
- this.reset();
- this.setState({selectedRule: _.cloneDeep(r)});
- }} className={classNames("row-small", "row-clickable", {"row-selected": rule.id === r.id})}>
- {r["id"].substring(0, 8)}
- {r["name"]}
-
- {r["notes"]}
-
- );
-
- let patterns = (this.state.selectedPattern == null && !isUpdate ?
- rule.patterns.concat(this.state.newPattern) :
- rule.patterns
- ).map(p => p === pattern ?
-
-
- {
- this.updateParam(() => pattern.regex = v);
- this.setState({patternRegexFocused: pattern.regex === ""});
- }}/>
-
- this.updateParam(() => pattern.flags.caseless = v)}/>
- this.updateParam(() => pattern.flags.dot_all = v)}/>
- this.updateParam(() => pattern.flags.multi_line = v)}/>
- this.updateParam(() => pattern.flags.utf_8_mode = v)}/>
- this.updateParam(() => pattern.flags.unicode_property = v)}/>
-
- this.updateParam(() => pattern.min_occurrences = v)}/>
-
-
- this.updateParam(() => pattern.max_occurrences = v)}/>
-
- s", "s->c"]}
- value={this.directions[pattern.direction]}
- onChange={(v) => this.updateParam(() => pattern.direction = v)}/>
- {this.state.selectedPattern == null ?
- this.addPattern(p)}/> :
- this.updatePattern(p)}/>}
-
-
- :
-
- {p.regex}
- {p.flags.caseless ? "yes" : "no"}
- {p.flags.dot_all ? "yes" : "no"}
- {p.flags.multi_line ? "yes" : "no"}
- {p.flags.utf_8_mode ? "yes" : "no"}
- {p.flags.unicode_property ? "yes" : "no"}
- {p.min_occurrences}
- {p.max_occurrences}
- {this.directions[p.direction]}
- {!isUpdate && this.editPattern(p)}/> }
-
- );
-
- return (
-
-
-
- GET /api/rules
- {this.state.rulesStatusCode &&
- }
-
-
-
-
-
-
-
- id
- name
- color
- notes
-
-
-
- {rules}
-
-
-
-
-
-
-
-
-
- {isUpdate ? `PUT /api/rules/${this.state.selectedRule.id}` : "POST /api/rules"}
-
-
-
-
-
-
-
-
- this.updateParam((r) => r.name = v)}
- error={this.state.ruleNameError}/>
- this.updateParam((r) => r.color = v)}/>
- this.updateParam((r) => r.notes = v)}/>
-
-
-
- filters:
- this.updateParam((r) => r.filter.service_port = v)}
- min={0} max={65565} error={this.state.ruleServicePortError}
- readonly={isUpdate}/>
- this.updateParam((r) => r.filter.client_port = v)}
- min={0} max={65565} error={this.state.ruleClientPortError}
- readonly={isUpdate}/>
- this.updateParam((r) => r.filter.client_address = v)}/>
-
-
-
- this.updateParam((r) => r.filter.min_duration = v)}/>
- this.updateParam((r) => r.filter.max_duration = v)}/>
- this.updateParam((r) => r.filter.min_bytes = v)}/>
- this.updateParam((r) => r.filter.max_bytes = v)}/>
-
-
-
-
-
-
-
-
- regex
- !Aa
- .*
- \n+
- UTF8
- Uni_
- min
- max
- direction
- {!isUpdate && actions }
-
-
-
- {patterns}
-
-
- {this.state.rulePatternsError != null &&
-
error: {this.state.rulePatternsError} }
-
-
-
-
- {}
-
-
-
-
- );
- }
-
-}
-
-export default RulePane;
diff --git a/frontend/src/components/panels/RulePane.scss b/frontend/src/components/panels/RulePane.scss
deleted file mode 100644
index 992445a..0000000
--- a/frontend/src/components/panels/RulePane.scss
+++ /dev/null
@@ -1,32 +0,0 @@
-
-.rule-pane {
- display: flex;
- flex-direction: column;
-
- .rules-list {
- overflow: hidden;
- flex: 2 1;
-
- .section-content {
- height: 100%;
- }
-
- .section-table {
- height: calc(100% - 30px);
- }
- }
-
- .rule-edit {
- display: flex;
- flex: 3 0;
- flex-direction: column;
-
- .section-content {
- flex: 1;
- }
-
- .section-table {
- max-height: 150px;
- }
- }
-}
diff --git a/frontend/src/components/panels/RulesPane.js b/frontend/src/components/panels/RulesPane.js
new file mode 100644
index 0000000..a66cde7
--- /dev/null
+++ b/frontend/src/components/panels/RulesPane.js
@@ -0,0 +1,438 @@
+/*
+ * This file is part of caronte (https://github.com/eciavatta/caronte).
+ * Copyright (c) 2020 Emiliano Ciavatta.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
.
+ */
+
+import React, {Component} from 'react';
+import './common.scss';
+import './RulesPane.scss';
+import Table from "react-bootstrap/Table";
+import {Col, Container, Row} from "react-bootstrap";
+import InputField from "../fields/InputField";
+import CheckField from "../fields/CheckField";
+import TextField from "../fields/TextField";
+import backend from "../../backend";
+import NumericField from "../fields/extensions/NumericField";
+import ColorField from "../fields/extensions/ColorField";
+import ChoiceField from "../fields/ChoiceField";
+import ButtonField from "../fields/ButtonField";
+import validation from "../../validation";
+import LinkPopover from "../objects/LinkPopover";
+import {randomClassName} from "../../utils";
+import dispatcher from "../../dispatcher";
+
+const classNames = require('classnames');
+const _ = require('lodash');
+
+class RulesPane extends Component {
+
+ emptyRule = {
+ "name": "",
+ "color": "",
+ "notes": "",
+ "enabled": true,
+ "patterns": [],
+ "filter": {
+ "service_port": 0,
+ "client_address": "",
+ "client_port": 0,
+ "min_duration": 0,
+ "max_duration": 0,
+ "min_bytes": 0,
+ "max_bytes": 0
+ },
+ "version": 0
+ };
+ emptyPattern = {
+ "regex": "",
+ "flags": {
+ "caseless": false,
+ "dot_all": false,
+ "multi_line": false,
+ "utf_8_mode": false,
+ "unicode_property": false
+ },
+ "min_occurrences": 0,
+ "max_occurrences": 0,
+ "direction": 0
+ };
+ state = {
+ rules: [],
+ newRule: this.emptyRule,
+ newPattern: this.emptyPattern
+ };
+
+ constructor(props) {
+ super(props);
+
+ this.directions = {
+ 0: "both",
+ 1: "c->s",
+ 2: "s->c"
+ };
+ }
+
+ componentDidMount() {
+ this.reset();
+ this.loadRules();
+
+ dispatcher.register("notifications", payload => {
+ if (payload.event === "rules.new" || payload.event === "rules.edit") {
+ this.loadRules();
+ }
+ });
+
+ document.title = "caronte:~/rules$";
+ }
+
+ loadRules = () => {
+ backend.get("/api/rules").then(res => this.setState({rules: res.json, rulesStatusCode: res.status}))
+ .catch(res => this.setState({rulesStatusCode: res.status, rulesResponse: JSON.stringify(res.json)}));
+ };
+
+ addRule = () => {
+ if (this.validateRule(this.state.newRule)) {
+ backend.post("/api/rules", this.state.newRule).then(res => {
+ this.reset();
+ this.setState({ruleStatusCode: res.status});
+ this.loadRules();
+ }).catch(res => {
+ this.setState({ruleStatusCode: res.status, ruleResponse: JSON.stringify(res.json)});
+ });
+ }
+ };
+
+ updateRule = () => {
+ const rule = this.state.selectedRule;
+ if (this.validateRule(rule)) {
+ backend.put(`/api/rules/${rule.id}`, rule).then(res => {
+ this.reset();
+ this.setState({ruleStatusCode: res.status});
+ this.loadRules();
+ }).catch(res => {
+ this.setState({ruleStatusCode: res.status, ruleResponse: JSON.stringify(res.json)});
+ });
+ }
+ };
+
+ validateRule = (rule) => {
+ let valid = true;
+ if (rule.name.length < 3) {
+ this.setState({ruleNameError: "name.length < 3"});
+ valid = false;
+ }
+ if (!validation.isValidColor(rule.color)) {
+ this.setState({ruleColorError: "color is not hexcolor"});
+ valid = false;
+ }
+ if (!validation.isValidPort(rule.filter.service_port)) {
+ this.setState({ruleServicePortError: "service_port > 65565"});
+ valid = false;
+ }
+ if (!validation.isValidPort(rule.filter.client_port)) {
+ this.setState({ruleClientPortError: "client_port > 65565"});
+ valid = false;
+ }
+ if (!validation.isValidAddress(rule.filter.client_address)) {
+ this.setState({ruleClientAddressError: "client_address is not ip_address"});
+ valid = false;
+ }
+ if (rule.filter.min_duration > rule.filter.max_duration) {
+ this.setState({ruleDurationError: "min_duration > max_dur."});
+ valid = false;
+ }
+ if (rule.filter.min_bytes > rule.filter.max_bytes) {
+ this.setState({ruleBytesError: "min_bytes > max_bytes"});
+ valid = false;
+ }
+ if (rule.patterns.length < 1) {
+ this.setState({rulePatternsError: "patterns.length < 1"});
+ valid = false;
+ }
+
+ return valid;
+ };
+
+ reset = () => {
+ const newRule = _.cloneDeep(this.emptyRule);
+ const newPattern = _.cloneDeep(this.emptyPattern);
+ this.setState({
+ selectedRule: null,
+ newRule: newRule,
+ selectedPattern: null,
+ newPattern: newPattern,
+ patternRegexFocused: false,
+ patternOccurrencesFocused: false,
+ ruleNameError: null,
+ ruleColorError: null,
+ ruleServicePortError: null,
+ ruleClientPortError: null,
+ ruleClientAddressError: null,
+ ruleDurationError: null,
+ ruleBytesError: null,
+ rulePatternsError: null,
+ ruleStatusCode: null,
+ rulesStatusCode: null,
+ ruleResponse: null,
+ rulesResponse: null
+ });
+ };
+
+ updateParam = (callback) => {
+ const updatedRule = this.currentRule();
+ callback(updatedRule);
+ this.setState({newRule: updatedRule});
+ };
+
+ currentRule = () => this.state.selectedRule != null ? this.state.selectedRule : this.state.newRule;
+
+ addPattern = (pattern) => {
+ if (!this.validatePattern(pattern)) {
+ return;
+ }
+
+ const newPattern = _.cloneDeep(this.emptyPattern);
+ this.currentRule().patterns.push(pattern);
+ this.setState({
+ newPattern: newPattern
+ });
+ };
+
+ editPattern = (pattern) => {
+ this.setState({
+ selectedPattern: pattern
+ });
+ };
+
+ updatePattern = (pattern) => {
+ if (!this.validatePattern(pattern)) {
+ return;
+ }
+
+ this.setState({
+ selectedPattern: null
+ });
+ };
+
+ validatePattern = (pattern) => {
+ let valid = true;
+ if (pattern.regex === "") {
+ valid = false;
+ this.setState({patternRegexFocused: true});
+ }
+ if (pattern.min_occurrences > pattern.max_occurrences) {
+ valid = false;
+ this.setState({patternOccurrencesFocused: true});
+ }
+ return valid;
+ };
+
+ render() {
+ const isUpdate = this.state.selectedRule != null;
+ const rule = this.currentRule();
+ const pattern = this.state.selectedPattern || this.state.newPattern;
+
+ let rules = this.state.rules.map(r =>
+
{
+ this.reset();
+ this.setState({selectedRule: _.cloneDeep(r)});
+ }} className={classNames("row-small", "row-clickable", {"row-selected": rule.id === r.id})}>
+ {r["id"].substring(0, 8)}
+ {r["name"]}
+
+ {r["notes"]}
+
+ );
+
+ let patterns = (this.state.selectedPattern == null && !isUpdate ?
+ rule.patterns.concat(this.state.newPattern) :
+ rule.patterns
+ ).map(p => p === pattern ?
+
+
+ {
+ this.updateParam(() => pattern.regex = v);
+ this.setState({patternRegexFocused: pattern.regex === ""});
+ }}/>
+
+ this.updateParam(() => pattern.flags.caseless = v)}/>
+ this.updateParam(() => pattern.flags.dot_all = v)}/>
+ this.updateParam(() => pattern.flags.multi_line = v)}/>
+ this.updateParam(() => pattern.flags.utf_8_mode = v)}/>
+ this.updateParam(() => pattern.flags.unicode_property = v)}/>
+
+ this.updateParam(() => pattern.min_occurrences = v)}/>
+
+
+ this.updateParam(() => pattern.max_occurrences = v)}/>
+
+ s", "s->c"]}
+ value={this.directions[pattern.direction]}
+ onChange={(v) => this.updateParam(() => pattern.direction = v)}/>
+ {this.state.selectedPattern == null ?
+ this.addPattern(p)}/> :
+ this.updatePattern(p)}/>}
+
+
+ :
+
+ {p.regex}
+ {p.flags.caseless ? "yes" : "no"}
+ {p.flags.dot_all ? "yes" : "no"}
+ {p.flags.multi_line ? "yes" : "no"}
+ {p.flags.utf_8_mode ? "yes" : "no"}
+ {p.flags.unicode_property ? "yes" : "no"}
+ {p.min_occurrences}
+ {p.max_occurrences}
+ {this.directions[p.direction]}
+ {!isUpdate && this.editPattern(p)}/> }
+
+ );
+
+ return (
+
+
+
+ GET /api/rules
+ {this.state.rulesStatusCode &&
+ }
+
+
+
+
+
+
+
+ id
+ name
+ color
+ notes
+
+
+
+ {rules}
+
+
+
+
+
+
+
+
+
+ {isUpdate ? `PUT /api/rules/${this.state.selectedRule.id}` : "POST /api/rules"}
+
+
+
+
+
+
+
+
+ this.updateParam((r) => r.name = v)}
+ error={this.state.ruleNameError}/>
+ this.updateParam((r) => r.color = v)}/>
+ this.updateParam((r) => r.notes = v)}/>
+
+
+
+ filters:
+ this.updateParam((r) => r.filter.service_port = v)}
+ min={0} max={65565} error={this.state.ruleServicePortError}
+ readonly={isUpdate}/>
+ this.updateParam((r) => r.filter.client_port = v)}
+ min={0} max={65565} error={this.state.ruleClientPortError}
+ readonly={isUpdate}/>
+ this.updateParam((r) => r.filter.client_address = v)}/>
+
+
+
+ this.updateParam((r) => r.filter.min_duration = v)}/>
+ this.updateParam((r) => r.filter.max_duration = v)}/>
+ this.updateParam((r) => r.filter.min_bytes = v)}/>
+ this.updateParam((r) => r.filter.max_bytes = v)}/>
+
+
+
+
+
+
+
+
+ regex
+ !Aa
+ .*
+ \n+
+ UTF8
+ Uni_
+ min
+ max
+ direction
+ {!isUpdate && actions }
+
+
+
+ {patterns}
+
+
+ {this.state.rulePatternsError != null &&
+
error: {this.state.rulePatternsError} }
+
+
+
+
+ {}
+
+
+
+
+ );
+ }
+
+}
+
+export default RulesPane;
diff --git a/frontend/src/components/panels/RulesPane.scss b/frontend/src/components/panels/RulesPane.scss
new file mode 100644
index 0000000..992445a
--- /dev/null
+++ b/frontend/src/components/panels/RulesPane.scss
@@ -0,0 +1,32 @@
+
+.rule-pane {
+ display: flex;
+ flex-direction: column;
+
+ .rules-list {
+ overflow: hidden;
+ flex: 2 1;
+
+ .section-content {
+ height: 100%;
+ }
+
+ .section-table {
+ height: calc(100% - 30px);
+ }
+ }
+
+ .rule-edit {
+ display: flex;
+ flex: 3 0;
+ flex-direction: column;
+
+ .section-content {
+ flex: 1;
+ }
+
+ .section-table {
+ max-height: 150px;
+ }
+ }
+}
diff --git a/frontend/src/components/panels/ServicePane.js b/frontend/src/components/panels/ServicePane.js
deleted file mode 100644
index fc7004b..0000000
--- a/frontend/src/components/panels/ServicePane.js
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * This file is part of caronte (https://github.com/eciavatta/caronte).
- * Copyright (c) 2020 Emiliano Ciavatta.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
.
- */
-
-import React, {Component} from 'react';
-import './common.scss';
-import './ServicePane.scss';
-import Table from "react-bootstrap/Table";
-import {Col, Container, Row} from "react-bootstrap";
-import InputField from "../fields/InputField";
-import TextField from "../fields/TextField";
-import backend from "../../backend";
-import NumericField from "../fields/extensions/NumericField";
-import ColorField from "../fields/extensions/ColorField";
-import ButtonField from "../fields/ButtonField";
-import validation from "../../validation";
-import LinkPopover from "../objects/LinkPopover";
-import {createCurlCommand} from "../../utils";
-import dispatcher from "../../dispatcher";
-
-const classNames = require('classnames');
-const _ = require('lodash');
-
-class ServicePane extends Component {
-
- emptyService = {
- "port": 0,
- "name": "",
- "color": "",
- "notes": ""
- };
-
- state = {
- services: [],
- currentService: this.emptyService,
- };
-
- componentDidMount() {
- this.reset();
- this.loadServices();
-
- dispatcher.register("notifications", payload => {
- if (payload.event === "services.edit") {
- this.loadServices();
- }
- });
-
- document.title = "caronte:~/services$";
- }
-
- loadServices = () => {
- backend.get("/api/services")
- .then(res => this.setState({services: Object.values(res.json), servicesStatusCode: res.status}))
- .catch(res => this.setState({servicesStatusCode: res.status, servicesResponse: JSON.stringify(res.json)}));
- };
-
- updateService = () => {
- const service = this.state.currentService;
- if (this.validateService(service)) {
- backend.put("/api/services", service).then(res => {
- this.reset();
- this.setState({serviceStatusCode: res.status});
- this.loadServices();
- }).catch(res => {
- this.setState({serviceStatusCode: res.status, serviceResponse: JSON.stringify(res.json)});
- });
- }
- };
-
- validateService = (service) => {
- let valid = true;
- if (!validation.isValidPort(service.port, true)) {
- this.setState({servicePortError: "port < 0 || port > 65565"});
- valid = false;
- }
- if (service.name.length < 3) {
- this.setState({serviceNameError: "name.length < 3"});
- valid = false;
- }
- if (!validation.isValidColor(service.color)) {
- this.setState({serviceColorError: "color is not hexcolor"});
- valid = false;
- }
-
- return valid;
- };
-
- reset = () => {
- this.setState({
- isUpdate: false,
- currentService: _.cloneDeep(this.emptyService),
- servicePortError: null,
- serviceNameError: null,
- serviceColorError: null,
- serviceStatusCode: null,
- servicesStatusCode: null,
- serviceResponse: null,
- servicesResponse: null
- });
- };
-
- updateParam = (callback) => {
- callback(this.state.currentService);
- this.setState({currentService: this.state.currentService});
- };
-
- render() {
- const isUpdate = this.state.isUpdate;
- const service = this.state.currentService;
-
- let services = this.state.services.map(s =>
-
{
- this.reset();
- this.setState({isUpdate: true, currentService: _.cloneDeep(s)});
- }} className={classNames("row-small", "row-clickable", {"row-selected": service.port === s.port })}>
- {s["port"]}
- {s["name"]}
-
- {s["notes"]}
-
- );
-
- const curlCommand = createCurlCommand("/services", "PUT", service);
-
- return (
-
-
-
- GET /api/services
- {this.state.servicesStatusCode &&
- }
-
-
-
-
-
-
-
- port
- name
- color
- notes
-
-
-
- {services}
-
-
-
-
-
-
-
-
- PUT /api/services
-
-
-
-
-
-
-
- this.updateParam((s) => s.port = v)}
- min={0} max={65565} error={this.state.servicePortError} />
- this.updateParam((s) => s.name = v)}
- error={this.state.serviceNameError} />
- this.updateParam((s) => s.color = v)} />
-
-
-
- this.updateParam((s) => s.notes = v)} />
-
-
-
-
-
-
-
-
- {}
-
-
-
-
- );
- }
-
-}
-
-export default ServicePane;
diff --git a/frontend/src/components/panels/ServicePane.scss b/frontend/src/components/panels/ServicePane.scss
deleted file mode 100644
index daf7e79..0000000
--- a/frontend/src/components/panels/ServicePane.scss
+++ /dev/null
@@ -1,22 +0,0 @@
-
-.service-pane {
- display: flex;
- flex-direction: column;
-
- .services-list {
- overflow: hidden;
- flex: 1;
-
- .section-content {
- height: 100%;
- }
-
- .section-table {
- height: calc(100% - 30px);
- }
- }
-
- .service-edit {
- flex: 0;
- }
-}
diff --git a/frontend/src/components/panels/ServicesPane.js b/frontend/src/components/panels/ServicesPane.js
new file mode 100644
index 0000000..bc82356
--- /dev/null
+++ b/frontend/src/components/panels/ServicesPane.js
@@ -0,0 +1,212 @@
+/*
+ * This file is part of caronte (https://github.com/eciavatta/caronte).
+ * Copyright (c) 2020 Emiliano Ciavatta.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
.
+ */
+
+import React, {Component} from 'react';
+import './common.scss';
+import './ServicesPane.scss';
+import Table from "react-bootstrap/Table";
+import {Col, Container, Row} from "react-bootstrap";
+import InputField from "../fields/InputField";
+import TextField from "../fields/TextField";
+import backend from "../../backend";
+import NumericField from "../fields/extensions/NumericField";
+import ColorField from "../fields/extensions/ColorField";
+import ButtonField from "../fields/ButtonField";
+import validation from "../../validation";
+import LinkPopover from "../objects/LinkPopover";
+import {createCurlCommand} from "../../utils";
+import dispatcher from "../../dispatcher";
+
+const classNames = require('classnames');
+const _ = require('lodash');
+
+class ServicesPane extends Component {
+
+ emptyService = {
+ "port": 0,
+ "name": "",
+ "color": "",
+ "notes": ""
+ };
+
+ state = {
+ services: [],
+ currentService: this.emptyService,
+ };
+
+ componentDidMount() {
+ this.reset();
+ this.loadServices();
+
+ dispatcher.register("notifications", payload => {
+ if (payload.event === "services.edit") {
+ this.loadServices();
+ }
+ });
+
+ document.title = "caronte:~/services$";
+ }
+
+ loadServices = () => {
+ backend.get("/api/services")
+ .then(res => this.setState({services: Object.values(res.json), servicesStatusCode: res.status}))
+ .catch(res => this.setState({servicesStatusCode: res.status, servicesResponse: JSON.stringify(res.json)}));
+ };
+
+ updateService = () => {
+ const service = this.state.currentService;
+ if (this.validateService(service)) {
+ backend.put("/api/services", service).then(res => {
+ this.reset();
+ this.setState({serviceStatusCode: res.status});
+ this.loadServices();
+ }).catch(res => {
+ this.setState({serviceStatusCode: res.status, serviceResponse: JSON.stringify(res.json)});
+ });
+ }
+ };
+
+ validateService = (service) => {
+ let valid = true;
+ if (!validation.isValidPort(service.port, true)) {
+ this.setState({servicePortError: "port < 0 || port > 65565"});
+ valid = false;
+ }
+ if (service.name.length < 3) {
+ this.setState({serviceNameError: "name.length < 3"});
+ valid = false;
+ }
+ if (!validation.isValidColor(service.color)) {
+ this.setState({serviceColorError: "color is not hexcolor"});
+ valid = false;
+ }
+
+ return valid;
+ };
+
+ reset = () => {
+ this.setState({
+ isUpdate: false,
+ currentService: _.cloneDeep(this.emptyService),
+ servicePortError: null,
+ serviceNameError: null,
+ serviceColorError: null,
+ serviceStatusCode: null,
+ servicesStatusCode: null,
+ serviceResponse: null,
+ servicesResponse: null
+ });
+ };
+
+ updateParam = (callback) => {
+ callback(this.state.currentService);
+ this.setState({currentService: this.state.currentService});
+ };
+
+ render() {
+ const isUpdate = this.state.isUpdate;
+ const service = this.state.currentService;
+
+ let services = this.state.services.map(s =>
+
{
+ this.reset();
+ this.setState({isUpdate: true, currentService: _.cloneDeep(s)});
+ }} className={classNames("row-small", "row-clickable", {"row-selected": service.port === s.port })}>
+ {s["port"]}
+ {s["name"]}
+
+ {s["notes"]}
+
+ );
+
+ const curlCommand = createCurlCommand("/services", "PUT", service);
+
+ return (
+
+
+
+ GET /api/services
+ {this.state.servicesStatusCode &&
+ }
+
+
+
+
+
+
+
+ port
+ name
+ color
+ notes
+
+
+
+ {services}
+
+
+
+
+
+
+
+
+ PUT /api/services
+
+
+
+
+
+
+
+ this.updateParam((s) => s.port = v)}
+ min={0} max={65565} error={this.state.servicePortError} />
+ this.updateParam((s) => s.name = v)}
+ error={this.state.serviceNameError} />
+ this.updateParam((s) => s.color = v)} />
+
+
+
+ this.updateParam((s) => s.notes = v)} />
+
+
+
+
+
+
+
+
+ {}
+
+
+
+
+ );
+ }
+
+}
+
+export default ServicesPane;
diff --git a/frontend/src/components/panels/ServicesPane.scss b/frontend/src/components/panels/ServicesPane.scss
new file mode 100644
index 0000000..daf7e79
--- /dev/null
+++ b/frontend/src/components/panels/ServicesPane.scss
@@ -0,0 +1,22 @@
+
+.service-pane {
+ display: flex;
+ flex-direction: column;
+
+ .services-list {
+ overflow: hidden;
+ flex: 1;
+
+ .section-content {
+ height: 100%;
+ }
+
+ .section-table {
+ height: calc(100% - 30px);
+ }
+ }
+
+ .service-edit {
+ flex: 0;
+ }
+}
diff --git a/frontend/src/components/panels/StreamsPane.js b/frontend/src/components/panels/StreamsPane.js
new file mode 100644
index 0000000..c8bd121
--- /dev/null
+++ b/frontend/src/components/panels/StreamsPane.js
@@ -0,0 +1,242 @@
+/*
+ * This file is part of caronte (https://github.com/eciavatta/caronte).
+ * Copyright (c) 2020 Emiliano Ciavatta.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
.
+ */
+
+import React, {Component} from 'react';
+import './StreamsPane.scss';
+import {Row} from 'react-bootstrap';
+import MessageAction from "../objects/MessageAction";
+import backend from "../../backend";
+import ButtonField from "../fields/ButtonField";
+import ChoiceField from "../fields/ChoiceField";
+import DOMPurify from 'dompurify';
+import ReactJson from 'react-json-view'
+import {downloadBlob, getHeaderValue} from "../../utils";
+import log from "../../log";
+
+const classNames = require('classnames');
+
+class StreamsPane extends Component {
+
+ state = {
+ messages: [],
+ format: "default",
+ tryParse: true
+ };
+
+ constructor(props) {
+ super(props);
+
+ this.validFormats = ["default", "hex", "hexdump", "base32", "base64", "ascii", "binary", "decimal", "octal"];
+ }
+
+ componentDidMount() {
+ if (this.props.connection && this.state.currentId !== this.props.connection.id) {
+ this.setState({currentId: this.props.connection.id});
+ this.loadStream(this.props.connection.id);
+ }
+
+ document.title = "caronte:~/$";
+ }
+
+ componentDidUpdate(prevProps, prevState, snapshot) {
+ if (this.props.connection && (
+ this.props.connection !== prevProps.connection || this.state.format !== prevState.format)) {
+ this.closeRenderWindow();
+ this.loadStream(this.props.connection.id);
+ }
+ }
+
+ componentWillUnmount() {
+ this.closeRenderWindow();
+ }
+
+ loadStream = (connectionId) => {
+ this.setState({messages: []});
+ backend.get(`/api/streams/${connectionId}?format=${this.state.format}`)
+ .then(res => this.setState({messages: res.json}));
+ };
+
+ setFormat = (format) => {
+ if (this.validFormats.includes(format)) {
+ this.setState({format: format});
+ }
+ };
+
+ tryParseConnectionMessage = (connectionMessage) => {
+ if (connectionMessage.metadata == null) {
+ return connectionMessage.content;
+ }
+ if (connectionMessage["is_metadata_continuation"]) {
+ return
**already parsed in previous messages** ;
+ }
+
+ let unrollMap = (obj) => obj == null ? null : Object.entries(obj).map(([key, value]) =>
+
{key} : {value}
+ );
+
+ let m = connectionMessage.metadata;
+ switch (m.type) {
+ case "http-request":
+ let url =
{m.host}{m.url} ;
+ return
+ {m.method} {url} {m.protocol}
+ {unrollMap(m.headers)}
+ {m.body}
+ {unrollMap(m.trailers)}
+ ;
+ case "http-response":
+ const contentType = getHeaderValue(m, "Content-Type");
+ let body = m.body;
+ if (contentType && contentType.includes("application/json")) {
+ try {
+ const json = JSON.parse(m.body);
+ body =
;
+ } catch (e) {
+ console.log(e);
+ }
+ }
+
+ return
+ {m.protocol} {m.status}
+ {unrollMap(m.headers)}
+ {body}
+ {unrollMap(m.trailers)}
+ ;
+ default:
+ return connectionMessage.content;
+ }
+ };
+
+ connectionsActions = (connectionMessage) => {
+ if (!connectionMessage.metadata) {
+ return null;
+ }
+
+ const m = connectionMessage.metadata;
+ switch (m.type) {
+ case "http-request" :
+ if (!connectionMessage.metadata["reproducers"]) {
+ return;
+ }
+ return Object.entries(connectionMessage.metadata["reproducers"]).map(([actionName, actionValue]) =>
+ {
+ this.setState({
+ messageActionDialog: this.setState({messageActionDialog: null})}/>
+ });
+ }}/>
+ );
+ case "http-response":
+ const contentType = getHeaderValue(m, "Content-Type");
+
+ if (contentType && contentType.includes("text/html")) {
+ return {
+ let w;
+ if (this.state.renderWindow && !this.state.renderWindow.closed) {
+ w = this.state.renderWindow;
+ } else {
+ w = window.open("", "", "width=900, height=600, scrollbars=yes");
+ this.setState({renderWindow: w});
+ }
+ w.document.body.innerHTML = DOMPurify.sanitize(m.body);
+ w.focus();
+ }}/>;
+ }
+ break;
+ default:
+ return null;
+ }
+ };
+
+ downloadStreamRaw = (value) => {
+ if (this.state.currentId) {
+ backend.download(`/api/streams/${this.props.connection.id}/download?format=${this.state.format}&type=${value}`)
+ .then(res => downloadBlob(res.blob, `${this.state.currentId}-${value}-${this.state.format}.txt`))
+ .catch(_ => log.error("Failed to download stream messages"));
+ }
+ };
+
+ closeRenderWindow = () => {
+ if (this.state.renderWindow) {
+ this.state.renderWindow.close();
+ }
+ };
+
+ render() {
+ const conn = this.props.connection || {
+ "ip_src": "0.0.0.0",
+ "ip_dst": "0.0.0.0",
+ "port_src": "0",
+ "port_dst": "0",
+ "started_at": new Date().toISOString(),
+ };
+ const content = this.state.messages || [];
+
+ let payload = content.map((c, i) =>
+
+
+
+
+ offset : {c.index} | timestamp : {c.timestamp}
+ | retransmitted : {c["is_retransmitted"] ? "yes" : "no"}
+
+
{this.connectionsActions(c)}
+
+
+
{c["from_client"] ? "client" : "server"}
+
+ {this.state.tryParse && this.state.format === "default" ? this.tryParseConnectionMessage(c) : c.content}
+
+
+ );
+
+ return (
+
+
+
+
+ flow : {conn["ip_src"]}:{conn["port_src"]} -> {conn["ip_dst"]}:{conn["port_dst"]}
+ | timestamp : {conn["started_at"]}
+
+
+
+
+
+
+
+
+
+
+
+
{payload}
+ {this.state.messageActionDialog}
+
+ );
+ }
+
+}
+
+
+export default StreamsPane;
diff --git a/frontend/src/components/panels/StreamsPane.scss b/frontend/src/components/panels/StreamsPane.scss
new file mode 100644
index 0000000..d5510cf
--- /dev/null
+++ b/frontend/src/components/panels/StreamsPane.scss
@@ -0,0 +1,113 @@
+@import "../../colors";
+
+.connection-content {
+ height: 100%;
+ background-color: $color-primary-0;
+
+ pre {
+ overflow-x: hidden;
+ height: calc(100% - 31px);
+ padding: 0 10px;
+ white-space: pre-wrap;
+ word-break: break-word;
+
+ p {
+ margin: 0;
+ padding: 0;
+ }
+ }
+
+ .connection-message {
+ position: relative;
+ margin: 10px 0;
+ border: 4px solid $color-primary-3;
+ border-top: 0;
+
+ .connection-message-header {
+ height: 25px;
+ background-color: $color-primary-3;
+
+ .connection-message-info {
+ font-size: 11px;
+ margin-top: 6px;
+ margin-left: -10px;
+ }
+
+ .connection-message-actions {
+ display: none;
+ margin-right: -18px;
+
+ button {
+ font-size: 11px;
+ margin: 0 3px;
+ padding: 5px;
+ }
+ }
+ }
+
+ .message-content {
+ padding: 10px;
+
+ .react-json-view {
+ background-color: inherit !important;
+ }
+ }
+
+ &:hover .connection-message-actions {
+ display: flex;
+ }
+
+ .connection-message-label {
+ font-size: 12px;
+ position: absolute;
+ top: 0;
+ padding: 10px 0;
+ background-color: $color-primary-3;
+ writing-mode: vertical-rl;
+ text-orientation: mixed;
+ }
+
+ &.from-client {
+ margin-right: 100px;
+ color: $color-primary-4;
+
+ .connection-message-label {
+ right: -22px;
+ }
+ }
+
+ &.from-server {
+ margin-left: 100px;
+ color: $color-primary-4;
+
+ .connection-message-label {
+ left: -22px;
+ transform: rotate(-180deg);
+ }
+ }
+ }
+
+ .connection-content-header {
+ height: 33px;
+ padding: 0;
+ background-color: $color-primary-3;
+
+ .header-info {
+ font-size: 12px;
+ padding-top: 7px;
+ padding-left: 25px;
+ }
+
+ .header-actions {
+ display: flex;
+
+ .choice-field {
+ margin-top: -5px;
+
+ .field-value {
+ background-color: $color-primary-3;
+ }
+ }
+ }
+ }
+}
diff --git a/frontend/src/index.js b/frontend/src/index.js
index e3e48de..ca1273a 100644
--- a/frontend/src/index.js
+++ b/frontend/src/index.js
@@ -19,7 +19,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';
import './index.scss';
-import App from './views/App';
+import App from './components/App';
import * as serviceWorker from './serviceWorker';
import notifications from "./notifications";
diff --git a/frontend/src/views/App.js b/frontend/src/views/App.js
deleted file mode 100644
index 8105117..0000000
--- a/frontend/src/views/App.js
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * This file is part of caronte (https://github.com/eciavatta/caronte).
- * Copyright (c) 2020 Emiliano Ciavatta.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import React, {Component} from 'react';
-import './App.scss';
-import Header from "./Header";
-import MainPane from "../components/panels/MainPane";
-import Timeline from "./Timeline";
-import {BrowserRouter as Router} from "react-router-dom";
-import Filters from "./Filters";
-import ConfigurationPane from "../components/panels/ConfigurationPane";
-import Notifications from "../components/Notifications";
-import dispatcher from "../dispatcher";
-
-class App extends Component {
-
- state = {};
-
- componentDidMount() {
- dispatcher.register("notifications", payload => {
- if (payload.event === "connected") {
- this.setState({
- connected: true,
- configured: payload.message["is_configured"]
- });
- }
- });
-
- setInterval(() => {
- if (document.title.endsWith("❚")) {
- document.title = document.title.slice(0, -1);
- } else {
- document.title += "❚";
- }
- }, 500);
- }
-
- render() {
- let modal;
- if (this.state.filterWindowOpen && this.state.configured) {
- modal = this.setState({filterWindowOpen: false})}/>;
- }
-
- return (
-
-
- {this.state.connected &&
-
-
- this.setState({filterWindowOpen: true})}/>
-
-
- {this.state.configured ? :
- this.setState({configured: true})}/>}
- {modal}
-
-
- {this.state.configured && }
-
-
- }
-
- );
- }
-}
-
-export default App;
diff --git a/frontend/src/views/App.scss b/frontend/src/views/App.scss
deleted file mode 100644
index 87661c3..0000000
--- a/frontend/src/views/App.scss
+++ /dev/null
@@ -1,16 +0,0 @@
-
-.main {
- display: flex;
- flex-direction: column;
- height: 100vh;
-
- .main-content {
- overflow: hidden;
- flex: 1 1;
- }
-
- .main-header,
- .main-footer {
- flex: 0 0;
- }
-}
diff --git a/frontend/src/views/Connections.js b/frontend/src/views/Connections.js
deleted file mode 100644
index b2edd3f..0000000
--- a/frontend/src/views/Connections.js
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * This file is part of caronte (https://github.com/eciavatta/caronte).
- * Copyright (c) 2020 Emiliano Ciavatta.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import React, {Component} from 'react';
-import './Connections.scss';
-import Connection from "../components/Connection";
-import Table from 'react-bootstrap/Table';
-import {Redirect} from 'react-router';
-import {withRouter} from "react-router-dom";
-import backend from "../backend";
-import ConnectionMatchedRules from "../components/ConnectionMatchedRules";
-import log from "../log";
-import ButtonField from "../components/fields/ButtonField";
-import dispatcher from "../dispatcher";
-
-class Connections extends Component {
-
- state = {
- loading: false,
- connections: [],
- firstConnection: null,
- lastConnection: null,
- };
-
- constructor(props) {
- super(props);
-
- this.scrollTopThreashold = 0.00001;
- this.scrollBottomThreashold = 0.99999;
- this.maxConnections = 200;
- this.queryLimit = 50;
- this.connectionsListRef = React.createRef();
- this.lastScrollPosition = 0;
- this.doQueryStringRedirect = false;
- this.doSelectedConnectionRedirect = false;
- }
-
- componentDidMount() {
- this.loadConnections({limit: this.queryLimit})
- .then(() => this.setState({loaded: true}));
- if (this.props.initialConnection) {
- this.setState({selected: this.props.initialConnection.id});
- }
-
- dispatcher.register("timeline_updates", payload => {
- this.connectionsListRef.current.scrollTop = 0;
- this.loadConnections({
- started_after: Math.round(payload.from.getTime() / 1000),
- started_before: Math.round(payload.to.getTime() / 1000),
- limit: this.maxConnections
- }).then(() => log.info(`Loading connections between ${payload.from} and ${payload.to}`));
- });
-
- dispatcher.register("notifications", payload => {
- if (payload.event === "rules.new" || payload.event === "rules.edit") {
- this.loadRules().then(() => log.debug("Loaded connection rules after notification update"));
- }
- });
-
- dispatcher.register("notifications", payload => {
- if (payload.event === "services.edit") {
- this.loadServices().then(() => log.debug("Services reloaded after notification update"));
- }
- });
- }
-
- connectionSelected = (c) => {
- this.doSelectedConnectionRedirect = true;
- this.setState({selected: c.id});
- this.props.onSelected(c);
- };
-
- componentDidUpdate(prevProps, prevState, snapshot) {
- if (this.state.loaded && prevProps.location.search !== this.props.location.search) {
- this.loadConnections({limit: this.queryLimit})
- .then(() => log.info("Connections reloaded after query string update"));
- }
- }
-
- handleScroll = (e) => {
- if (this.disableScrollHandler) {
- this.lastScrollPosition = e.currentTarget.scrollTop;
- return;
- }
-
- let relativeScroll = e.currentTarget.scrollTop / (e.currentTarget.scrollHeight - e.currentTarget.clientHeight);
- if (!this.state.loading && relativeScroll > this.scrollBottomThreashold) {
- this.loadConnections({from: this.state.lastConnection.id, limit: this.queryLimit,})
- .then(() => log.info("Following connections loaded"));
- }
- if (!this.state.loading && relativeScroll < this.scrollTopThreashold) {
- this.loadConnections({to: this.state.firstConnection.id, limit: this.queryLimit,})
- .then(() => log.info("Previous connections loaded"));
- if (this.state.showMoreRecentButton) {
- this.setState({showMoreRecentButton: false});
- }
- } else {
- if (this.lastScrollPosition > e.currentTarget.scrollTop) {
- if (!this.state.showMoreRecentButton) {
- this.setState({showMoreRecentButton: true});
- }
- } else {
- if (this.state.showMoreRecentButton) {
- this.setState({showMoreRecentButton: false});
- }
- }
- }
- this.lastScrollPosition = e.currentTarget.scrollTop;
- };
-
- addServicePortFilter = (port) => {
- const urlParams = new URLSearchParams(this.props.location.search);
- urlParams.set("service_port", port);
- this.doQueryStringRedirect = true;
- this.setState({queryString: urlParams});
- };
-
- addMatchedRulesFilter = (matchedRule) => {
- const urlParams = new URLSearchParams(this.props.location.search);
- const oldMatchedRules = urlParams.getAll("matched_rules") || [];
-
- if (!oldMatchedRules.includes(matchedRule)) {
- urlParams.append("matched_rules", matchedRule);
- this.doQueryStringRedirect = true;
- this.setState({queryString: urlParams});
- }
- };
-
- async loadConnections(params) {
- let url = "/api/connections";
- const urlParams = new URLSearchParams(this.props.location.search);
- for (const [name, value] of Object.entries(params)) {
- urlParams.set(name, value);
- }
-
- this.setState({loading: true});
- if (!this.state.rules) {
- await this.loadRules();
- }
- if (!this.state.services) {
- await this.loadServices();
- }
-
- let res = (await backend.get(`${url}?${urlParams}`)).json;
-
- let connections = this.state.connections;
- let firstConnection = this.state.firstConnection;
- let lastConnection = this.state.lastConnection;
-
- if (params !== undefined && params.from !== undefined && params.to === undefined) {
- if (res.length > 0) {
- connections = this.state.connections.concat(res.slice(1));
- lastConnection = connections[connections.length - 1];
- if (connections.length > this.maxConnections) {
- connections = connections.slice(connections.length - this.maxConnections,
- connections.length - 1);
- firstConnection = connections[0];
- }
- }
- } else if (params !== undefined && params.to !== undefined && params.from === undefined) {
- if (res.length > 0) {
- connections = res.slice(0, res.length - 1).concat(this.state.connections);
- firstConnection = connections[0];
- if (connections.length > this.maxConnections) {
- connections = connections.slice(0, this.maxConnections);
- lastConnection = connections[this.maxConnections - 1];
- }
- }
- } else {
- if (res.length > 0) {
- connections = res;
- firstConnection = connections[0];
- lastConnection = connections[connections.length - 1];
- } else {
- connections = [];
- firstConnection = null;
- lastConnection = null;
- }
- }
-
- this.setState({
- loading: false,
- connections: connections,
- firstConnection: firstConnection,
- lastConnection: lastConnection
- });
-
- if (firstConnection != null && lastConnection != null) {
- dispatcher.dispatch("connection_updates", {
- from: new Date(lastConnection["started_at"]),
- to: new Date(firstConnection["started_at"])
- });
- }
- }
-
- loadRules = async () => {
- return backend.get("/api/rules").then(res => this.setState({rules: res.json}));
- };
-
- loadServices = async () => {
- return backend.get("/api/services").then(res => this.setState({services: res.json}));
- };
-
- render() {
- let redirect;
- if (this.doSelectedConnectionRedirect) {
- redirect = ;
- this.doSelectedConnectionRedirect = false;
- } else if (this.doQueryStringRedirect) {
- redirect = ;
- this.doQueryStringRedirect = false;
- }
-
- let loading = null;
- if (this.state.loading) {
- loading =
- Loading...
- ;
- }
-
- return (
-
- {this.state.showMoreRecentButton &&
- {
- this.disableScrollHandler = true;
- this.connectionsListRef.current.scrollTop = 0;
- this.loadConnections({limit: this.queryLimit})
- .then(() => {
- this.disableScrollHandler = false;
- log.info("Most recent connections loaded");
- });
- }}/>
-
}
-
-
-
-
-
- service
- srcip
- srcport
- dstip
- dstport
- started_at
- duration
- up
- down
- actions
-
-
-
- {
- this.state.connections.flatMap(c => {
- return [ this.connectionSelected(c)}
- selected={this.state.selected === c.id}
- onMarked={marked => c.marked = marked}
- onEnabled={enabled => c.hidden = !enabled}
- addServicePortFilter={this.addServicePortFilter}
- services={this.state.services}/>,
- c.matched_rules.length > 0 &&
-
- ];
- })
- }
- {loading}
-
-
-
- {redirect}
-
-
- );
- }
-
-}
-
-export default withRouter(Connections);
diff --git a/frontend/src/views/Connections.scss b/frontend/src/views/Connections.scss
deleted file mode 100644
index de06096..0000000
--- a/frontend/src/views/Connections.scss
+++ /dev/null
@@ -1,38 +0,0 @@
-@import "../colors.scss";
-
-.connections-container {
- position: relative;
- height: 100%;
- background-color: $color-primary-3;
-
- .connections {
- position: relative;
- overflow-y: scroll;
- height: 100%;
-
- .table {
- margin-bottom: 0;
- }
-
- th {
- font-size: 13.5px;
- position: sticky;
- top: 0;
- padding: 5px;
- border: none;
- background-color: $color-primary-3;
- }
-
- &:hover::-webkit-scrollbar-thumb {
- background: $color-secondary-2;
- }
- }
-
- .most-recent-button {
- position: absolute;
- z-index: 20;
- top: 45px;
- left: calc(50% - 50px);
- background-color: red;
- }
-}
diff --git a/frontend/src/views/Filters.js b/frontend/src/views/Filters.js
deleted file mode 100644
index 3dd8280..0000000
--- a/frontend/src/views/Filters.js
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * This file is part of caronte (https://github.com/eciavatta/caronte).
- * Copyright (c) 2020 Emiliano Ciavatta.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import React, {Component} from 'react';
-import {Col, Container, Modal, Row, Table} from "react-bootstrap";
-import {filtersDefinitions, filtersNames} from "../components/filters/FiltersDefinitions";
-import ButtonField from "../components/fields/ButtonField";
-
-class Filters extends Component {
-
- constructor(props) {
- super(props);
- let newState = {};
- filtersNames.forEach(elem => newState[`${elem}_active`] = false);
- this.state = newState;
- }
-
- componentDidMount() {
- let newState = {};
- filtersNames.forEach(elem => newState[`${elem}_active`] = localStorage.getItem(`filters.${elem}`) === "true");
- this.setState(newState);
- }
-
- checkboxChangesHandler(filterName, event) {
- this.setState({[`${filterName}_active`]: event.target.checked});
- localStorage.setItem(`filters.${filterName}`, event.target.checked);
- if (typeof window !== "undefined") {
- window.dispatchEvent(new Event("quick-filters"));
- }
- }
-
- generateRows(filtersNames) {
- return filtersNames.map(name =>
-
- this.checkboxChangesHandler(name, event)}/>
- {filtersDefinitions[name]}
-
- );
- }
-
- render() {
- return (
-
-
-
- ~/filters
-
-
-
-
-
-
-
-
-
- show
- filter
-
-
-
- {this.generateRows(["service_port", "client_address", "min_duration",
- "min_bytes", "started_after", "closed_after", "marked"])}
-
-
-
-
-
-
-
- show
- filter
-
-
-
- {this.generateRows(["matched_rules", "client_port", "max_duration",
- "max_bytes", "started_before", "closed_before", "hidden"])}
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
- }
-}
-
-export default Filters;
diff --git a/frontend/src/views/Header.js b/frontend/src/views/Header.js
deleted file mode 100644
index 2cfe9fb..0000000
--- a/frontend/src/views/Header.js
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * This file is part of caronte (https://github.com/eciavatta/caronte).
- * Copyright (c) 2020 Emiliano Ciavatta.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import React, {Component} from 'react';
-import Typed from 'typed.js';
-import './Header.scss';
-import {filtersDefinitions, filtersNames} from "../components/filters/FiltersDefinitions";
-import {Link, withRouter} from "react-router-dom";
-import ButtonField from "../components/fields/ButtonField";
-
-class Header extends Component {
-
- constructor(props) {
- super(props);
- let newState = {};
- filtersNames.forEach(elem => newState[`${elem}_active`] = false);
- this.state = newState;
- this.fetchStateFromLocalStorage = this.fetchStateFromLocalStorage.bind(this);
- }
-
- componentDidMount() {
- const options = {
- strings: ["caronte$ "],
- typeSpeed: 50,
- cursorChar: "❚"
- };
- this.typed = new Typed(this.el, options);
-
- this.fetchStateFromLocalStorage();
-
- if (typeof window !== "undefined") {
- window.addEventListener("quick-filters", this.fetchStateFromLocalStorage);
- }
- }
-
- componentWillUnmount() {
- this.typed.destroy();
-
- if (typeof window !== "undefined") {
- window.removeEventListener("quick-filters", this.fetchStateFromLocalStorage);
- }
- }
-
- fetchStateFromLocalStorage() {
- let newState = {};
- filtersNames.forEach(elem => newState[`${elem}_active`] = localStorage.getItem(`filters.${elem}`) === "true");
- this.setState(newState);
- }
-
- render() {
- let quickFilters = filtersNames.filter(name => this.state[`${name}_active`])
- .map(name => {filtersDefinitions[name]} )
- .slice(0, 5);
-
- return (
-
- );
- }
-}
-
-export default withRouter(Header);
diff --git a/frontend/src/views/Header.scss b/frontend/src/views/Header.scss
deleted file mode 100644
index 0711159..0000000
--- a/frontend/src/views/Header.scss
+++ /dev/null
@@ -1,34 +0,0 @@
-@import "../colors.scss";
-
-.header {
- height: 80px;
- padding: 15px 30px;
-
- > .row {
- background-color: $color-primary-0;
- }
-
- .header-title {
- width: 200px;
- margin: 5px 0 5px -5px;
- }
-
- .header-buttons {
- display: flex;
- justify-content: flex-end;
- margin: 7px 0;
-
- .button-field {
- margin-left: 7px;
- }
- }
-
- .filters-bar {
- padding: 3px 0;
-
- .filter {
- display: inline-block;
- margin-right: 10px;
- }
- }
-}
diff --git a/frontend/src/views/Timeline.js b/frontend/src/views/Timeline.js
deleted file mode 100644
index ebe3eb9..0000000
--- a/frontend/src/views/Timeline.js
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * This file is part of caronte (https://github.com/eciavatta/caronte).
- * Copyright (c) 2020 Emiliano Ciavatta.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import React, {Component} from 'react';
-import './Timeline.scss';
-import {
- ChartContainer,
- ChartRow,
- Charts,
- LineChart,
- MultiBrush,
- Resizable,
- styler,
- YAxis
-} from "react-timeseries-charts";
-import {TimeRange, TimeSeries} from "pondjs";
-import backend from "../backend";
-import ChoiceField from "../components/fields/ChoiceField";
-import {withRouter} from "react-router-dom";
-import log from "../log";
-import dispatcher from "../dispatcher";
-
-const minutes = 60 * 1000;
-
-class Timeline extends Component {
-
- state = {
- metric: "connections_per_service"
- };
-
- constructor() {
- super();
-
- this.disableTimeSeriesChanges = false;
- this.selectionTimeout = null;
- }
-
- filteredPort = () => {
- const urlParams = new URLSearchParams(this.props.location.search);
- return urlParams.get("service_port");
- };
-
- componentDidMount() {
- const filteredPort = this.filteredPort();
- this.setState({filteredPort});
- this.loadStatistics(this.state.metric, filteredPort).then(() => log.debug("Statistics loaded after mount"));
-
- dispatcher.register("connection_updates", payload => {
- this.setState({
- selection: new TimeRange(payload.from, payload.to),
- });
- });
-
- dispatcher.register("notifications", payload => {
- if (payload.event === "services.edit") {
- this.loadServices().then(() => log.debug("Services reloaded after notification update"));
- }
- });
- }
-
- componentDidUpdate(prevProps, prevState, snapshot) {
- const filteredPort = this.filteredPort();
- if (this.state.filteredPort !== filteredPort) {
- this.setState({filteredPort});
- this.loadStatistics(this.state.metric, filteredPort).then(() =>
- log.debug("Statistics reloaded after filtered port changes"));
- }
- }
-
- loadStatistics = async (metric, filteredPort) => {
- const urlParams = new URLSearchParams();
- urlParams.set("metric", metric);
-
- let services = await this.loadServices();
- if (filteredPort && services[filteredPort]) {
- const service = services[filteredPort];
- services = {};
- services[filteredPort] = service;
- }
-
- const ports = Object.keys(services);
- ports.forEach(s => urlParams.append("ports", s));
-
- const metrics = (await backend.get("/api/statistics?" + urlParams)).json;
- const zeroFilledMetrics = [];
- const toTime = m => new Date(m["range_start"]).getTime();
-
- if (metrics.length > 0) {
- let i = 0;
- for (let interval = toTime(metrics[0]); interval <= toTime(metrics[metrics.length - 1]); interval += minutes) {
- if (interval === toTime(metrics[i])) {
- const m = metrics[i++];
- m["range_start"] = new Date(m["range_start"]);
- zeroFilledMetrics.push(m);
- } else {
- const m = {};
- m["range_start"] = new Date(interval);
- m[metric] = {};
- ports.forEach(p => m[metric][p] = 0);
- zeroFilledMetrics.push(m);
- }
- }
- }
-
- const series = new TimeSeries({
- name: "statistics",
- columns: ["time"].concat(ports),
- points: zeroFilledMetrics.map(m => [m["range_start"]].concat(ports.map(p => m[metric][p] || 0)))
- });
- const start = series.range().begin();
- const end = series.range().end();
- start.setTime(start.getTime() - minutes);
- end.setTime(end.getTime() + minutes);
-
- this.setState({
- metric,
- series,
- timeRange: new TimeRange(start, end),
- start,
- end
- });
- log.debug(`Loaded statistics for metric "${metric}" for services [${ports}]`);
- };
-
- loadServices = async () => {
- const services = (await backend.get("/api/services")).json;
- this.setState({services});
- return services;
- };
-
- createStyler = () => {
- return styler(Object.keys(this.state.services).map(port => {
- return {key: port, color: this.state.services[port].color, width: 2};
- }));
- };
-
- handleTimeRangeChange = (timeRange) => {
- if (!this.disableTimeSeriesChanges) {
- this.setState({timeRange});
- }
- };
-
- handleSelectionChange = (timeRange) => {
- this.disableTimeSeriesChanges = true;
-
- this.setState({selection: timeRange});
- if (this.selectionTimeout) {
- clearTimeout(this.selectionTimeout);
- }
- this.selectionTimeout = setTimeout(() => {
- dispatcher.dispatch("timeline_updates", {
- from: timeRange.begin(),
- to: timeRange.end()
- });
- this.selectionTimeout = null;
- this.disableTimeSeriesChanges = false;
- }, 1000);
- };
-
- aggregateSeries = (func) => {
- const values = this.state.series.columns().map(c => this.state.series[func](c));
- return Math[func](...values);
- };
-
- render() {
- if (!this.state.series) {
- return null;
- }
-
- return (
-
- );
- }
-}
-
-export default withRouter(Timeline);
diff --git a/frontend/src/views/Timeline.scss b/frontend/src/views/Timeline.scss
deleted file mode 100644
index 14360d4..0000000
--- a/frontend/src/views/Timeline.scss
+++ /dev/null
@@ -1,22 +0,0 @@
-@import "../colors.scss";
-
-.footer {
- padding: 15px;
-
- .time-line {
- position: relative;
- background-color: $color-primary-0;
-
- .metric-selection {
- font-size: 0.8em;
- position: absolute;
- top: 5px;
- right: 10px;
- }
- }
-
- svg text {
- font-family: "Fira Code", monospace !important;
- fill: $color-primary-4 !important;
- }
-}
--
cgit v1.2.3-70-g09d2
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/objects')
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 08456e7f2e1c1af6fc8fdbf580c0178a25b93f8b Mon Sep 17 00:00:00 2001
From: Emiliano Ciavatta
Date: Thu, 15 Oct 2020 08:53:09 +0200
Subject: General improvements
---
frontend/public/logo128.png | Bin 0 -> 6498 bytes
frontend/public/logo192.png | Bin 6498 -> 0 bytes
frontend/public/manifest.json | 8 +-
frontend/src/components/Header.js | 2 +-
frontend/src/components/Timeline.js | 122 +++++++++++++++------
frontend/src/components/Timeline.scss | 1 +
.../components/filters/BooleanConnectionsFilter.js | 64 ++++-------
.../src/components/filters/ExitSearchFilter.js | 9 +-
.../src/components/filters/FiltersDispatcher.js | 57 ----------
.../components/filters/RulesConnectionsFilter.js | 81 ++++++--------
.../components/filters/StringConnectionsFilter.js | 77 ++++++-------
frontend/src/components/objects/Connection.js | 4 +-
.../components/objects/ConnectionMatchedRules.js | 21 ++--
frontend/src/components/pages/MainPage.js | 3 -
frontend/src/components/panels/ConnectionsPane.js | 122 ++++++++++++---------
frontend/src/components/panels/SearchPane.scss | 1 +
frontend/src/components/panels/StreamsPane.js | 2 +-
frontend/src/dispatcher.js | 6 +
search_controller.go | 4 +
statistics_controller.go | 19 ++--
20 files changed, 299 insertions(+), 304 deletions(-)
create mode 100644 frontend/public/logo128.png
delete mode 100644 frontend/public/logo192.png
delete mode 100644 frontend/src/components/filters/FiltersDispatcher.js
(limited to 'frontend/src/components/objects')
diff --git a/frontend/public/logo128.png b/frontend/public/logo128.png
new file mode 100644
index 0000000..1969e1d
Binary files /dev/null and b/frontend/public/logo128.png differ
diff --git a/frontend/public/logo192.png b/frontend/public/logo192.png
deleted file mode 100644
index 1969e1d..0000000
Binary files a/frontend/public/logo192.png and /dev/null differ
diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json
index 0409a59..32674ce 100644
--- a/frontend/public/manifest.json
+++ b/frontend/public/manifest.json
@@ -1,6 +1,6 @@
{
- "short_name": "Caronte",
- "name": "Caronte",
+ "short_name": "caronte",
+ "name": "caronte",
"icons": [
{
"src": "favicon.ico",
@@ -8,9 +8,9 @@
"type": "image/x-icon"
},
{
- "src": "logo192.png",
+ "src": "logo128.png",
"type": "image/png",
- "sizes": "192x192"
+ "sizes": "128x128"
},
{
"src": "logo512.png",
diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js
index b72b532..b4a2177 100644
--- a/frontend/src/components/Header.js
+++ b/frontend/src/components/Header.js
@@ -89,7 +89,7 @@ class Header extends Component {
- {/* */}
+
diff --git a/frontend/src/components/Timeline.js b/frontend/src/components/Timeline.js
index 6b8806f..bc42a01 100644
--- a/frontend/src/components/Timeline.js
+++ b/frontend/src/components/Timeline.js
@@ -35,8 +35,12 @@ import log from "../log";
import dispatcher from "../dispatcher";
const minutes = 60 * 1000;
+const _ = require('lodash');
const classNames = require('classnames');
+const leftSelectionPaddingMultiplier = 24;
+const rightSelectionPaddingMultiplier = 8;
+
class Timeline extends Component {
state = {
@@ -50,25 +54,30 @@ class Timeline extends Component {
this.selectionTimeout = null;
}
- filteredPort = () => {
+ additionalFilters = () => {
const urlParams = new URLSearchParams(this.props.location.search);
- return urlParams.get("service_port");
+ if (this.state.metric === "matched_rules") {
+ return urlParams.getAll("matched_rules") || [];
+ } else {
+ 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"));
+ const additionalFilters = this.additionalFilters();
+ this.setState({filters: additionalFilters});
+ this.loadStatistics(this.state.metric, additionalFilters).then(() => log.debug("Statistics loaded after mount"));
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.loadServices().then(() => log.debug("Services reloaded after notification update"));
+ this.loadServices().then(() => this.adjustSelection());
}
});
@@ -79,27 +88,48 @@ class Timeline extends Component {
}
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"));
+ const additionalFilters = this.additionalFilters();
+ const updateStatistics = () => {
+ this.setState({filters: additionalFilters});
+ this.loadStatistics(this.state.metric, additionalFilters).then(() =>
+ log.debug("Statistics reloaded after filters changes"));
+ };
+
+ if (this.state.metric === "matched_rules") {
+ if (!Array.isArray(this.state.filters) ||
+ !_.isEqual(_.sortBy(additionalFilters), _.sortBy(this.state.filters))) {
+ updateStatistics();
+ }
+ } else {
+ if (this.state.filters !== additionalFilters) {
+ updateStatistics();
+ }
}
}
- loadStatistics = async (metric, filteredPort) => {
+ loadStatistics = async (metric, filters) => {
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;
- }
+ let columns = [];
+ if (metric === "matched_rules") {
+ let rules = await this.loadRules();
+ filters.forEach(id => {
+ urlParams.append("matched_rules", id);
+ });
+ columns = rules.map(r => r.id);
+ } else {
+ let services = await this.loadServices();
+ const filteredPort = filters;
+ if (filteredPort && services[filters]) {
+ const service = services[filteredPort];
+ services = {};
+ services[filteredPort] = service;
+ }
- const ports = Object.keys(services);
- ports.forEach(s => urlParams.append("ports", s));
+ columns = Object.keys(services);
+ columns.forEach(port => urlParams.append("ports", port));
+ }
const metrics = (await backend.get("/api/statistics?" + urlParams)).json;
if (metrics.length === 0) {
@@ -109,8 +139,8 @@ class Timeline extends Component {
const zeroFilledMetrics = [];
const toTime = m => new Date(m["range_start"]).getTime();
let i = 0;
- for (let interval = toTime(metrics[0]); interval <= toTime(metrics[metrics.length - 1]); interval += minutes) {
- if (interval === toTime(metrics[i])) {
+ for (let interval = toTime(metrics[0]) - minutes; interval <= toTime(metrics[metrics.length - 1]) + minutes; interval += minutes) {
+ if (i < metrics.length && interval === toTime(metrics[i])) {
const m = metrics[i++];
m["range_start"] = new Date(m["range_start"]);
zeroFilledMetrics.push(m);
@@ -118,30 +148,31 @@ class Timeline extends Component {
const m = {};
m["range_start"] = new Date(interval);
m[metric] = {};
- ports.forEach(p => m[metric][p] = 0);
+ columns.forEach(c => m[metric][c] = 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)))
+ columns: ["time"].concat(columns),
+ points: zeroFilledMetrics.map(m => [m["range_start"]].concat(columns.map(c =>
+ ((metric in m) && (m[metric] != null)) ? (m[metric][c] || 0) : 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),
+ columns,
start,
end
});
- log.debug(`Loaded statistics for metric "${metric}" for services [${ports}]`);
+ log.debug(`Loaded statistics for metric "${metric}"`);
};
loadServices = async () => {
@@ -150,10 +181,22 @@ class Timeline extends Component {
return services;
};
+ loadRules = async () => {
+ const rules = (await backend.get("/api/rules")).json;
+ this.setState({rules});
+ return rules;
+ };
+
createStyler = () => {
- return styler(Object.keys(this.state.services).map(port => {
- return {key: port, color: this.state.services[port].color, width: 2};
- }));
+ if (this.state.metric === "matched_rules") {
+ return styler(this.state.rules.map(rule => {
+ return {key: rule.id, color: rule.color, width: 2};
+ }));
+ } else {
+ return styler(Object.keys(this.state.services).map(port => {
+ return {key: port, color: this.state.services[port].color, width: 2};
+ }));
+ }
};
handleTimeRangeChange = (timeRange) => {
@@ -179,6 +222,15 @@ class Timeline extends Component {
}, 1000);
};
+ adjustSelection = () => {
+ const seriesRange = this.state.series.range();
+ const selection = this.state.selection;
+ const delta = selection.end() - selection.begin();
+ const start = Math.max(selection.begin().getTime() - delta * leftSelectionPaddingMultiplier, seriesRange.begin().getTime());
+ const end = Math.min(selection.end().getTime() + delta * rightSelectionPaddingMultiplier, seriesRange.end().getTime());
+ this.setState({timeRange: new TimeRange(start, end)});
+ };
+
aggregateSeries = (func) => {
const values = this.state.series.columns().map(c => this.state.series[func](c));
return Math[func](...values);
@@ -207,7 +259,7 @@ class Timeline extends Component {
max={this.aggregateSeries("max")} width="35" type="linear" transition={300}/>
this.loadStatistics(metric, this.state.filteredPort)
+ "server_bytes_per_service", "duration_per_service", "matched_rules"]}
+ onChange={(metric) => this.loadStatistics(metric, this.state.filters)
.then(() => log.debug("Statistics loaded after metric changes"))}
value={this.state.metric}/>
diff --git a/frontend/src/components/Timeline.scss b/frontend/src/components/Timeline.scss
index db8d9c8..262da1e 100644
--- a/frontend/src/components/Timeline.scss
+++ b/frontend/src/components/Timeline.scss
@@ -12,6 +12,7 @@
position: absolute;
top: 5px;
right: 10px;
+ width: 180px;
}
&.pulse-timeline {
diff --git a/frontend/src/components/filters/BooleanConnectionsFilter.js b/frontend/src/components/filters/BooleanConnectionsFilter.js
index a9a420e..c611a0d 100644
--- a/frontend/src/components/filters/BooleanConnectionsFilter.js
+++ b/frontend/src/components/filters/BooleanConnectionsFilter.js
@@ -17,65 +17,49 @@
import React, {Component} from 'react';
import {withRouter} from "react-router-dom";
-import {Redirect} from "react-router";
import CheckField from "../fields/CheckField";
+import dispatcher from "../../dispatcher";
class BooleanConnectionsFilter extends Component {
- constructor(props) {
- super(props);
- this.state = {
- filterActive: "false"
- };
-
- this.filterChanged = this.filterChanged.bind(this);
- this.needRedirect = false;
- }
+ state = {
+ filterActive: "false"
+ };
componentDidMount() {
let params = new URLSearchParams(this.props.location.search);
this.setState({filterActive: this.toBoolean(params.get(this.props.filterName)).toString()});
+
+ this.connectionsFiltersCallback = payload => {
+ const name = this.props.filterName;
+ if (name in payload && this.state.filterActive !== payload[name]) {
+ this.setState({filterActive: payload[name]});
+ }
+ };
+ dispatcher.register("connections_filters", this.connectionsFiltersCallback);
}
- componentDidUpdate(prevProps, prevState, snapshot) {
- let urlParams = new URLSearchParams(this.props.location.search);
- let externalActive = this.toBoolean(urlParams.get(this.props.filterName));
- let filterActive = this.toBoolean(this.state.filterActive);
- // if the filterActive state is changed by another component (and not by filterChanged func) and
- // the query string is not equals at the filterActive state, update the state of the component
- if (this.toBoolean(prevState.filterActive) === filterActive && filterActive !== externalActive) {
- this.setState({filterActive: externalActive.toString()});
- }
+ componentWillUnmount() {
+ dispatcher.unregister(this.connectionsFiltersCallback);
}
- toBoolean(value) {
+ toBoolean = (value) => {
return value !== null && value.toLowerCase() === "true";
- }
+ };
- filterChanged() {
- this.needRedirect = true;
- this.setState({filterActive: (!this.toBoolean(this.state.filterActive)).toString()});
- }
+ filterChanged = () => {
+ const newValue = (!this.toBoolean(this.state.filterActive)).toString();
+ const urlParams = {};
+ urlParams[this.props.filterName] = newValue === "true" ? "true" : null;
+ dispatcher.dispatch("connections_filters", urlParams);
+ this.setState({filterActive: newValue});
+ };
render() {
- let redirect = null;
- if (this.needRedirect) {
- let urlParams = new URLSearchParams(this.props.location.search);
- if (this.toBoolean(this.state.filterActive)) {
- urlParams.set(this.props.filterName, "true");
- } else {
- urlParams.delete(this.props.filterName);
- }
- redirect =
;
-
- this.needRedirect = false;
- }
-
return (
- {redirect}
+ onChange={this.filterChanged}/>
);
}
diff --git a/frontend/src/components/filters/ExitSearchFilter.js b/frontend/src/components/filters/ExitSearchFilter.js
index cfee298..68ca686 100644
--- a/frontend/src/components/filters/ExitSearchFilter.js
+++ b/frontend/src/components/filters/ExitSearchFilter.js
@@ -28,11 +28,16 @@ class ExitSearchFilter extends Component {
let params = new URLSearchParams(this.props.location.search);
this.setState({performedSearch: params.get("performed_search")});
- dispatcher.register("connections_filters", payload => {
+ this.connectionsFiltersCallback = payload => {
if (this.state.performedSearch !== payload["performed_search"]) {
this.setState({performedSearch: payload["performed_search"]});
}
- });
+ };
+ dispatcher.register("connections_filters", this.connectionsFiltersCallback);
+ }
+
+ componentWillUnmount() {
+ dispatcher.unregister(this.connectionsFiltersCallback);
}
render() {
diff --git a/frontend/src/components/filters/FiltersDispatcher.js b/frontend/src/components/filters/FiltersDispatcher.js
deleted file mode 100644
index 3769055..0000000
--- a/frontend/src/components/filters/FiltersDispatcher.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * This file is part of caronte (https://github.com/eciavatta/caronte).
- * Copyright (c) 2020 Emiliano Ciavatta.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
.
- */
-
-import React, {Component} from 'react';
-import {withRouter} from "react-router-dom";
-import {Redirect} from "react-router";
-import dispatcher from "../../dispatcher";
-
-class FiltersDispatcher extends Component {
-
- state = {};
-
- componentDidMount() {
- let params = new URLSearchParams(this.props.location.search);
- this.setState({params});
-
- dispatcher.register("connections_filters", payload => {
- const params = this.state.params;
-
- Object.entries(payload).forEach(([key, value]) => {
- if (value == null) {
- params.delete(key);
- } else {
- params.set(key, value);
- }
- });
-
- this.needRedirect = true;
- this.setState({params});
- });
- }
-
- render() {
- if (this.needRedirect) {
- this.needRedirect = false;
- return
;
- }
-
- return null;
- }
-}
-
-export default withRouter(FiltersDispatcher);
diff --git a/frontend/src/components/filters/RulesConnectionsFilter.js b/frontend/src/components/filters/RulesConnectionsFilter.js
index fc0ad4d..4c993dc 100644
--- a/frontend/src/components/filters/RulesConnectionsFilter.js
+++ b/frontend/src/components/filters/RulesConnectionsFilter.js
@@ -17,87 +17,74 @@
import React, {Component} from 'react';
import {withRouter} from "react-router-dom";
-import {Redirect} from "react-router";
import './RulesConnectionsFilter.scss';
import ReactTags from 'react-tag-autocomplete';
import backend from "../../backend";
+import dispatcher from "../../dispatcher";
const classNames = require('classnames');
+const _ = require('lodash');
class RulesConnectionsFilter extends Component {
- constructor(props) {
- super(props);
- this.state = {
- mounted: false,
- rules: [],
- activeRules: []
- };
-
- this.needRedirect = false;
- }
+ state = {
+ rules: [],
+ activeRules: []
+ };
componentDidMount() {
- let params = new URLSearchParams(this.props.location.search);
+ 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));
- this.setState({rules, activeRules, mounted: true});
+ this.setState({rules, activeRules});
});
+
+ 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));
+ this.setState({
+ activeRules: newRules.map(r => {
+ return {id: r.id, name: r.name};
+ })
+ });
+ }
+ };
+ dispatcher.register("connections_filters", this.connectionsFiltersCallback);
}
- componentDidUpdate(prevProps, prevState, snapshot) {
- let urlParams = new URLSearchParams(this.props.location.search);
- let externalRules = urlParams.getAll("matched_rules") || [];
- let activeRules = this.state.activeRules.map(r => r.id);
- let compareRules = (first, second) => first.sort().join(",") === second.sort().join(",");
- if (this.state.mounted &&
- compareRules(prevState.activeRules.map(r => r.id), activeRules) &&
- !compareRules(externalRules, activeRules)) {
- this.setState({activeRules: externalRules.map(id => this.state.rules.find(r => r.id === id))});
- }
+ componentWillUnmount() {
+ dispatcher.unregister(this.connectionsFiltersCallback);
}
- onDelete(i) {
- const activeRules = this.state.activeRules.slice(0);
+ onDelete = (i) => {
+ const activeRules = _.clone(this.state.activeRules);
activeRules.splice(i, 1);
- this.needRedirect = true;
- this.setState({ activeRules });
- }
+ this.setState({activeRules});
+ dispatcher.dispatch("connections_filters", {"matched_rules": activeRules.map(r => r.id)});
+ };
- onAddition(rule) {
+ onAddition = (rule) => {
if (!this.state.activeRules.includes(rule)) {
const activeRules = [].concat(this.state.activeRules, rule);
- this.needRedirect = true;
this.setState({activeRules});
+ dispatcher.dispatch("connections_filters", {"matched_rules": activeRules.map(r => r.id)});
}
- }
+ };
render() {
- let redirect = null;
-
- if (this.needRedirect) {
- let urlParams = new URLSearchParams(this.props.location.search);
- urlParams.delete("matched_rules");
- this.state.activeRules.forEach(rule => urlParams.append("matched_rules", rule.id));
- redirect = ;
-
- this.needRedirect = false;
- }
-
return (
-
+
- suggestion.name.startsWith(query) && !this.state.activeRules.includes(suggestion)} />
+ suggestion.name.startsWith(query) && !this.state.activeRules.includes(suggestion)}/>
-
- {redirect}
);
}
diff --git a/frontend/src/components/filters/StringConnectionsFilter.js b/frontend/src/components/filters/StringConnectionsFilter.js
index a3b45dc..c833220 100644
--- a/frontend/src/components/filters/StringConnectionsFilter.js
+++ b/frontend/src/components/filters/StringConnectionsFilter.js
@@ -17,37 +17,36 @@
import React, {Component} from 'react';
import {withRouter} from "react-router-dom";
-import {Redirect} from "react-router";
import InputField from "../fields/InputField";
+import dispatcher from "../../dispatcher";
class StringConnectionsFilter extends Component {
- constructor(props) {
- super(props);
- this.state = {
- fieldValue: "",
- filterValue: null,
- timeoutHandle: null,
- invalidValue: false
- };
- this.needRedirect = false;
- this.filterChanged = this.filterChanged.bind(this);
- }
+ state = {
+ fieldValue: "",
+ filterValue: null,
+ timeoutHandle: null,
+ invalidValue: false
+ };
componentDidMount() {
let params = new URLSearchParams(this.props.location.search);
this.updateStateFromFilterValue(params.get(this.props.filterName));
+
+ this.connectionsFiltersCallback = payload => {
+ const name = this.props.filterName;
+ if (name in payload && this.state.filterValue !== payload[name]) {
+ this.updateStateFromFilterValue(payload[name]);
+ }
+ };
+ dispatcher.register("connections_filters", this.connectionsFiltersCallback);
}
- componentDidUpdate(prevProps, prevState, snapshot) {
- let urlParams = new URLSearchParams(this.props.location.search);
- let filterValue = urlParams.get(this.props.filterName);
- if (prevState.filterValue === this.state.filterValue && this.state.filterValue !== filterValue) {
- this.updateStateFromFilterValue(filterValue);
- }
+ componentWillUnmount() {
+ dispatcher.unregister(this.connectionsFiltersCallback);
}
- updateStateFromFilterValue(filterValue) {
+ updateStateFromFilterValue = (filterValue) => {
if (filterValue !== null) {
let fieldValue = filterValue;
if (typeof this.props.decodeFunc === "function") {
@@ -70,15 +69,21 @@ class StringConnectionsFilter extends Component {
} else {
this.setState({fieldValue: "", filterValue: null});
}
- }
+ };
- isValueValid(value) {
+ isValueValid = (value) => {
return typeof this.props.validateFunc !== "function" ||
(typeof this.props.validateFunc === "function" && this.props.validateFunc(value));
- }
+ };
- filterChanged(fieldValue) {
- if (this.state.timeoutHandle !== null) {
+ changeFilterValue = (value) => {
+ const urlParams = {};
+ urlParams[this.props.filterName] = value;
+ dispatcher.dispatch("connections_filters", urlParams);
+ };
+
+ filterChanged = (fieldValue) => {
+ if (this.state.timeoutHandle) {
clearTimeout(this.state.timeoutHandle);
}
@@ -87,11 +92,12 @@ class StringConnectionsFilter extends Component {
}
if (fieldValue === "") {
- this.needRedirect = true;
this.setState({fieldValue: "", filterValue: null, invalidValue: false});
- return;
+ return this.changeFilterValue(null);
}
+
+
if (this.isValueValid(fieldValue)) {
let filterValue = fieldValue;
if (filterValue !== "" && typeof this.props.encodeFunc === "function") {
@@ -101,40 +107,27 @@ class StringConnectionsFilter extends Component {
this.setState({
fieldValue: fieldValue,
timeoutHandle: setTimeout(() => {
- this.needRedirect = true;
this.setState({filterValue: filterValue});
+ this.changeFilterValue(filterValue);
}, 500),
invalidValue: false
});
} else {
- this.needRedirect = true;
this.setState({
fieldValue: fieldValue,
invalidValue: true
});
}
- }
+ };
render() {
- let redirect = null;
- if (this.needRedirect) {
- let urlParams = new URLSearchParams(this.props.location.search);
- if (this.state.filterValue !== null) {
- urlParams.set(this.props.filterName, this.state.filterValue);
- } else {
- urlParams.delete(this.props.filterName);
- }
- redirect =
;
- this.needRedirect = false;
- }
let active = this.state.filterValue !== null;
return (
- {redirect}
+ value={this.state.fieldValue} inline={true} small={true}/>
);
}
diff --git a/frontend/src/components/objects/Connection.js b/frontend/src/components/objects/Connection.js
index e0e942a..96f2235 100644
--- a/frontend/src/components/objects/Connection.js
+++ b/frontend/src/components/objects/Connection.js
@@ -23,6 +23,7 @@ import {dateTimeToTime, durationBetween, formatSize} from "../../utils";
import ButtonField from "../fields/ButtonField";
import LinkPopover from "./LinkPopover";
import TextField from "../fields/TextField";
+import dispatcher from "../../dispatcher";
const classNames = require('classnames');
@@ -99,7 +100,8 @@ class Connection extends Component {
this.props.addServicePortFilter(conn["port_dst"])}/>
+ onClick={() => dispatcher.dispatch("connections_filters",
+ {"service_port": conn["port_dst"].toString()})}/>
{conn["ip_src"]}
diff --git a/frontend/src/components/objects/ConnectionMatchedRules.js b/frontend/src/components/objects/ConnectionMatchedRules.js
index 73d5c5d..92bde49 100644
--- a/frontend/src/components/objects/ConnectionMatchedRules.js
+++ b/frontend/src/components/objects/ConnectionMatchedRules.js
@@ -18,20 +18,25 @@
import React, {Component} from 'react';
import './ConnectionMatchedRules.scss';
import ButtonField from "../fields/ButtonField";
+import dispatcher from "../../dispatcher";
+import {withRouter} from "react-router-dom";
class ConnectionMatchedRules extends Component {
- constructor(props) {
- super(props);
- this.state = {
- };
- }
+ onMatchedRulesSelected = (id) => {
+ const params = new URLSearchParams(this.props.location.search);
+ const rules = params.getAll("matched_rules");
+ if (!rules.includes(id)) {
+ rules.push(id);
+ dispatcher.dispatch("connections_filters",{"matched_rules": rules});
+ }
+ };
render() {
const matchedRules = this.props.matchedRules.map(mr => {
const rule = this.props.rules.find(r => r.id === mr);
- return
this.props.addMatchedRulesFilter(rule.id)} name={rule.name}
- color={rule.color} small />;
+ return this.onMatchedRulesSelected(rule.id)} name={rule.name}
+ color={rule.color} small/>;
});
return (
@@ -43,4 +48,4 @@ class ConnectionMatchedRules extends Component {
}
}
-export default ConnectionMatchedRules;
+export default withRouter(ConnectionMatchedRules);
diff --git a/frontend/src/components/pages/MainPage.js b/frontend/src/components/pages/MainPage.js
index da57e1a..0b06f55 100644
--- a/frontend/src/components/pages/MainPage.js
+++ b/frontend/src/components/pages/MainPage.js
@@ -29,7 +29,6 @@ import Header from "../Header";
import Filters from "../dialogs/Filters";
import MainPane from "../panels/MainPane";
import SearchPane from "../panels/SearchPane";
-import FiltersDispatcher from "../filters/FiltersDispatcher";
class MainPage extends Component {
@@ -70,8 +69,6 @@ class MainPage extends Component {
-
-
);
diff --git a/frontend/src/components/panels/ConnectionsPane.js b/frontend/src/components/panels/ConnectionsPane.js
index 1f79ab8..33dd7c1 100644
--- a/frontend/src/components/panels/ConnectionsPane.js
+++ b/frontend/src/components/panels/ConnectionsPane.js
@@ -19,13 +19,13 @@ import React, {Component} from 'react';
import './ConnectionsPane.scss';
import Connection from "../objects/Connection";
import Table from 'react-bootstrap/Table';
-import {Redirect} from 'react-router';
import {withRouter} from "react-router-dom";
import backend from "../../backend";
import ConnectionMatchedRules from "../objects/ConnectionMatchedRules";
import log from "../../log";
import ButtonField from "../fields/ButtonField";
import dispatcher from "../../dispatcher";
+import {Redirect} from "react-router";
const classNames = require('classnames');
@@ -50,60 +50,91 @@ class ConnectionsPane extends Component {
}
componentDidMount() {
- const initialParams = {limit: this.queryLimit};
+ let urlParams = new URLSearchParams(this.props.location.search);
+ this.setState({urlParams});
+
+ const additionalParams = {limit: this.queryLimit};
const match = this.props.location.pathname.match(/^\/connections\/([a-f0-9]{24})$/);
if (match != null) {
const id = match[1];
- initialParams.from = id;
+ additionalParams.from = id;
backend.get(`/api/connections/${id}`)
- .then(res => this.connectionSelected(res.json, false))
+ .then(res => this.connectionSelected(res.json))
.catch(error => log.error("Error loading initial connection", error));
}
- this.loadConnections(initialParams, true).then(() => log.debug("Connections loaded"));
+ this.loadConnections(additionalParams, urlParams, true).then(() => log.debug("Connections loaded"));
+
+ this.connectionsFiltersCallback = payload => {
+ const params = this.state.urlParams;
+ const initialParams = params.toString();
+
+ Object.entries(payload).forEach(([key, value]) => {
+ if (value == null) {
+ params.delete(key);
+ } else if (Array.isArray(value)) {
+ params.delete(key);
+ value.forEach(v => params.append(key, v));
+ } else {
+ params.set(key, value);
+ }
+ });
+
+ if (initialParams === params.toString()) {
+ return;
+ }
- dispatcher.register("timeline_updates", payload => {
+ log.debug("Update following url params:", payload);
+ this.queryStringRedirect = true;
+ this.setState({urlParams});
+
+ this.loadConnections({limit: this.queryLimit}, urlParams)
+ .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);
- dispatcher.register("notifications", payload => {
+ this.notificationsCallback = payload => {
if (payload.event === "rules.new" || payload.event === "rules.edit") {
this.loadRules().then(() => log.debug("Loaded connection rules after notification update"));
}
- });
-
- dispatcher.register("notifications", payload => {
if (payload.event === "services.edit") {
this.loadServices().then(() => log.debug("Services reloaded after notification update"));
}
- });
+ };
+ dispatcher.register("notifications", this.notificationsCallback);
- dispatcher.register("pulse_connections_view", payload => {
+ this.pulseConnectionsViewCallback = payload => {
this.setState({pulseConnectionsView: true});
setTimeout(() => this.setState({pulseConnectionsView: false}), payload.duration);
- });
+ };
+ dispatcher.register("pulse_connections_view", this.pulseConnectionsViewCallback);
+ }
+
+ componentWillUnmount() {
+ dispatcher.unregister(this.timelineUpdatesCallback);
+ dispatcher.unregister(this.notificationsCallback);
+ dispatcher.unregister(this.pulseConnectionsViewCallback);
+ dispatcher.unregister(this.connectionsFiltersCallback);
}
- connectionSelected = (c, doRedirect = true) => {
- this.doSelectedConnectionRedirect = doRedirect;
+ connectionSelected = (c) => {
+ this.connectionSelectedRedirect = true;
this.setState({selected: c.id});
this.props.onSelected(c);
log.debug(`Connection ${c.id} selected`);
};
- componentDidUpdate(prevProps, prevState, snapshot) {
- if (prevProps.location.search !== this.props.location.search) {
- this.loadConnections({limit: this.queryLimit})
- .then(() => log.info("ConnectionsPane reloaded after query string update"));
- }
- }
-
handleScroll = (e) => {
if (this.disableScrollHandler) {
this.lastScrollPosition = e.currentTarget.scrollTop;
@@ -135,27 +166,12 @@ class ConnectionsPane extends Component {
this.lastScrollPosition = e.currentTarget.scrollTop;
};
- addServicePortFilter = (port) => {
- const urlParams = new URLSearchParams(this.props.location.search);
- urlParams.set("service_port", port);
- this.doQueryStringRedirect = true;
- this.setState({queryString: urlParams});
- };
-
- addMatchedRulesFilter = (matchedRule) => {
- const urlParams = new URLSearchParams(this.props.location.search);
- const oldMatchedRules = urlParams.getAll("matched_rules") || [];
-
- if (!oldMatchedRules.includes(matchedRule)) {
- urlParams.append("matched_rules", matchedRule);
- this.doQueryStringRedirect = true;
- this.setState({queryString: urlParams});
+ async loadConnections(additionalParams, initialParams = null, isInitial = false) {
+ if (!initialParams) {
+ initialParams = this.state.urlParams;
}
- };
-
- async loadConnections(params, isInitial = false) {
- const urlParams = new URLSearchParams(this.props.location.search);
- for (const [name, value] of Object.entries(params)) {
+ const urlParams = new URLSearchParams(initialParams.toString());
+ for (const [name, value] of Object.entries(additionalParams)) {
urlParams.set(name, value);
}
@@ -173,7 +189,7 @@ class ConnectionsPane extends Component {
let firstConnection = this.state.firstConnection;
let lastConnection = this.state.lastConnection;
- if (params !== undefined && params.from !== undefined && params.to === undefined) {
+ if (additionalParams !== undefined && additionalParams.from !== undefined && additionalParams.to === undefined) {
if (res.length > 0) {
if (!isInitial) {
res = res.slice(1);
@@ -189,7 +205,7 @@ class ConnectionsPane extends Component {
firstConnection = connections[0];
}
}
- } else if (params !== undefined && params.to !== undefined && params.from === undefined) {
+ } else if (additionalParams !== undefined && additionalParams.to !== undefined && additionalParams.from === undefined) {
if (res.length > 0) {
connections = res.slice(0, res.length - 1).concat(this.state.connections);
firstConnection = connections[0];
@@ -235,12 +251,12 @@ class ConnectionsPane extends Component {
render() {
let redirect;
- if (this.doSelectedConnectionRedirect) {
- redirect = ;
- this.doSelectedConnectionRedirect = false;
- } else if (this.doQueryStringRedirect) {
- redirect = ;
- this.doQueryStringRedirect = false;
+ if (this.connectionSelectedRedirect) {
+ redirect = ;
+ this.connectionSelectedRedirect = false;
+ } else if (this.queryStringRedirect) {
+ redirect = ;
+ this.queryStringRedirect = false;
}
let loading = null;
@@ -288,12 +304,10 @@ class ConnectionsPane extends Component {
selected={this.state.selected === c.id}
onMarked={marked => c.marked = marked}
onEnabled={enabled => c.hidden = !enabled}
- addServicePortFilter={this.addServicePortFilter}
services={this.state.services}/>,
c.matched_rules.length > 0 &&
+ rules={this.state.rules}/>
];
})
}
diff --git a/frontend/src/components/panels/SearchPane.scss b/frontend/src/components/panels/SearchPane.scss
index 15fc7da..63e11fb 100644
--- a/frontend/src/components/panels/SearchPane.scss
+++ b/frontend/src/components/panels/SearchPane.scss
@@ -4,6 +4,7 @@
.searches-list {
overflow: hidden;
+ flex: 2 1;
.section-content {
height: 100%;
diff --git a/frontend/src/components/panels/StreamsPane.js b/frontend/src/components/panels/StreamsPane.js
index bd1964e..1aa5c53 100644
--- a/frontend/src/components/panels/StreamsPane.js
+++ b/frontend/src/components/panels/StreamsPane.js
@@ -107,7 +107,7 @@ class StreamsPane extends Component {
const json = JSON.parse(m.body);
body = ;
} catch (e) {
- console.log(e);
+ log.error(e);
}
}
diff --git a/frontend/src/dispatcher.js b/frontend/src/dispatcher.js
index 943f7ec..fa08d48 100644
--- a/frontend/src/dispatcher.js
+++ b/frontend/src/dispatcher.js
@@ -15,6 +15,8 @@
* along with this program. If not, see .
*/
+const _ = require('lodash');
+
class Dispatcher {
constructor() {
@@ -44,6 +46,10 @@ class Dispatcher {
}
};
+ unregister = (callback) => {
+ this.listeners = _.without(callback);
+ };
+
}
const dispatcher = new Dispatcher();
diff --git a/search_controller.go b/search_controller.go
index 723cd93..5ed762a 100644
--- a/search_controller.go
+++ b/search_controller.go
@@ -76,6 +76,10 @@ func NewSearchController(storage Storage) *SearchController {
log.WithError(err).Panic("failed to retrieve performed searches")
}
+ if searches == nil {
+ searches = []PerformedSearch{}
+ }
+
return &SearchController{
storage: storage,
performedSearches: searches,
diff --git a/statistics_controller.go b/statistics_controller.go
index 57c7d95..fda7494 100644
--- a/statistics_controller.go
+++ b/statistics_controller.go
@@ -26,19 +26,19 @@ import (
type StatisticRecord struct {
RangeStart time.Time `json:"range_start" bson:"_id"`
- ConnectionsPerService map[uint16]int `json:"connections_per_service,omitempty" bson:"connections_per_service"`
- ClientBytesPerService map[uint16]int `json:"client_bytes_per_service,omitempty" bson:"client_bytes_per_service"`
- ServerBytesPerService map[uint16]int `json:"server_bytes_per_service,omitempty" bson:"server_bytes_per_service"`
- TotalBytesPerService map[uint16]int `json:"total_bytes_per_service,omitempty" bson:"total_bytes_per_service"`
- DurationPerService map[uint16]int64 `json:"duration_per_service,omitempty" bson:"duration_per_service"`
- MatchedRules map[RowID]int64 `json:"matched_rules,omitempty" bson:"matched_rules"`
+ ConnectionsPerService map[uint16]int `json:"connections_per_service" bson:"connections_per_service"`
+ ClientBytesPerService map[uint16]int `json:"client_bytes_per_service" bson:"client_bytes_per_service"`
+ ServerBytesPerService map[uint16]int `json:"server_bytes_per_service" bson:"server_bytes_per_service"`
+ TotalBytesPerService map[uint16]int `json:"total_bytes_per_service" bson:"total_bytes_per_service"`
+ DurationPerService map[uint16]int64 `json:"duration_per_service" bson:"duration_per_service"`
+ MatchedRules map[string]int64 `json:"matched_rules" bson:"matched_rules"`
}
type StatisticsFilter struct {
RangeFrom time.Time `form:"range_from"`
RangeTo time.Time `form:"range_to"`
Ports []uint16 `form:"ports"`
- RulesIDs []RowID `form:"rules_ids"`
+ RulesIDs []string `form:"rules_ids"`
Metric string `form:"metric"`
}
@@ -57,7 +57,7 @@ func NewStatisticsController(storage Storage) StatisticsController {
func (sc *StatisticsController) GetStatistics(context context.Context, filter StatisticsFilter) []StatisticRecord {
var statisticRecords []StatisticRecord
- query := sc.storage.Find(Statistics).Context(context)
+ query := sc.storage.Find(Statistics).Context(context).Sort("_id", true)
if !filter.RangeFrom.IsZero() {
query = query.Filter(OrderedDocument{{"_id", UnorderedDocument{"$lt": filter.RangeFrom}}})
}
@@ -81,7 +81,7 @@ func (sc *StatisticsController) GetStatistics(context context.Context, filter St
}
for _, ruleID := range filter.RulesIDs {
if filter.Metric == "" || filter.Metric == "matched_rules" {
- query = query.Projection(OrderedDocument{{fmt.Sprintf("matched_rules.%s", ruleID.Hex()), 1}})
+ query = query.Projection(OrderedDocument{{fmt.Sprintf("matched_rules.%s", ruleID), 1}})
}
}
@@ -91,6 +91,7 @@ func (sc *StatisticsController) GetStatistics(context context.Context, filter St
}
}
+ log.Println(query)
if err := query.All(&statisticRecords); err != nil {
log.WithError(err).WithField("filter", filter).Error("failed to retrieve statistics")
return []StatisticRecord{}
--
cgit v1.2.3-70-g09d2
From c745263e1b28e4cedffa88de764f11d6379d745d Mon Sep 17 00:00:00 2001
From: Emiliano Ciavatta
Date: Thu, 15 Oct 2020 13:50:37 +0200
Subject: Update rules filters
---
frontend/src/components/Timeline.js | 77 ++++++-------
frontend/src/components/dialogs/Filters.js | 2 +-
frontend/src/components/fields/TagField.js | 29 +++--
frontend/src/components/fields/TagField.scss | 99 +++++++++++------
frontend/src/components/fields/common.scss | 1 +
.../components/filters/RulesConnectionsFilter.js | 23 +---
.../components/filters/RulesConnectionsFilter.scss | 121 ---------------------
frontend/src/components/objects/MessageAction.js | 2 +-
frontend/src/components/panels/PcapsPane.js | 6 +-
frontend/src/components/panels/SearchPane.js | 14 +--
frontend/src/components/panels/StreamsPane.js | 2 +-
statistics_controller.go | 5 +-
12 files changed, 139 insertions(+), 242 deletions(-)
delete mode 100644 frontend/src/components/filters/RulesConnectionsFilter.scss
(limited to 'frontend/src/components/objects')
diff --git a/frontend/src/components/Timeline.js b/frontend/src/components/Timeline.js
index bc42a01..1d88bcb 100644
--- a/frontend/src/components/Timeline.js
+++ b/frontend/src/components/Timeline.js
@@ -35,7 +35,6 @@ import log from "../log";
import dispatcher from "../dispatcher";
const minutes = 60 * 1000;
-const _ = require('lodash');
const classNames = require('classnames');
const leftSelectionPaddingMultiplier = 24;
@@ -54,19 +53,26 @@ class Timeline extends Component {
this.selectionTimeout = null;
}
- additionalFilters = () => {
+ componentDidMount() {
const urlParams = new URLSearchParams(this.props.location.search);
- if (this.state.metric === "matched_rules") {
- return urlParams.getAll("matched_rules") || [];
- } else {
- return urlParams.get("service_port");
- }
- };
+ this.setState({
+ servicePortFilter: urlParams.get("service_port") || null,
+ matchedRulesFilter: urlParams.getAll("matched_rules") || null
+ });
- componentDidMount() {
- const additionalFilters = this.additionalFilters();
- this.setState({filters: additionalFilters});
- this.loadStatistics(this.state.metric, additionalFilters).then(() => log.debug("Statistics loaded after mount"));
+ 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({
@@ -76,8 +82,10 @@ class Timeline extends Component {
});
dispatcher.register("notifications", payload => {
- if (payload.event === "services.edit") {
- this.loadServices().then(() => this.adjustSelection());
+ if (payload.event === "services.edit" && this.state.metric !== "matched_rules") {
+ this.loadServices().then(() => log.debug("Statistics reloaded after services updates"));
+ } else if (payload.event.startsWith("rules") && this.state.metric === "matched_rules") {
+ this.loadServices().then(() => log.debug("Statistics reloaded after rules updates"));
}
});
@@ -87,41 +95,29 @@ class Timeline extends Component {
});
}
- componentDidUpdate(prevProps, prevState, snapshot) {
- const additionalFilters = this.additionalFilters();
- const updateStatistics = () => {
- this.setState({filters: additionalFilters});
- this.loadStatistics(this.state.metric, additionalFilters).then(() =>
- log.debug("Statistics reloaded after filters changes"));
- };
-
- if (this.state.metric === "matched_rules") {
- if (!Array.isArray(this.state.filters) ||
- !_.isEqual(_.sortBy(additionalFilters), _.sortBy(this.state.filters))) {
- updateStatistics();
- }
- } else {
- if (this.state.filters !== additionalFilters) {
- updateStatistics();
- }
- }
+ componentWillUnmount() {
+ dispatcher.unregister(this.connectionsFiltersCallback);
}
- loadStatistics = async (metric, filters) => {
+ loadStatistics = async (metric) => {
const urlParams = new URLSearchParams();
urlParams.set("metric", metric);
let columns = [];
if (metric === "matched_rules") {
let rules = await this.loadRules();
- filters.forEach(id => {
- urlParams.append("matched_rules", id);
- });
- columns = rules.map(r => r.id);
+ if (this.state.matchedRulesFilter.length > 0) {
+ this.state.matchedRulesFilter.forEach(id => {
+ urlParams.append("rules_ids", id);
+ });
+ columns = this.state.matchedRulesFilter;
+ } else {
+ columns = rules.map(r => r.id);
+ }
} else {
let services = await this.loadServices();
- const filteredPort = filters;
- if (filteredPort && services[filters]) {
+ const filteredPort = this.state.servicePortFilter;
+ if (filteredPort && services[filteredPort]) {
const service = services[filteredPort];
services = {};
services[filteredPort] = service;
@@ -172,7 +168,6 @@ class Timeline extends Component {
start,
end
});
- log.debug(`Loaded statistics for metric "${metric}"`);
};
loadServices = async () => {
@@ -279,7 +274,7 @@ class Timeline extends Component {
"server_bytes_per_service", "duration_per_service", "matched_rules"]}
values={["connections_per_service", "client_bytes_per_service",
"server_bytes_per_service", "duration_per_service", "matched_rules"]}
- onChange={(metric) => this.loadStatistics(metric, this.state.filters)
+ onChange={(metric) => this.loadStatistics(metric)
.then(() => log.debug("Statistics loaded after metric changes"))}
value={this.state.metric}/>
diff --git a/frontend/src/components/dialogs/Filters.js b/frontend/src/components/dialogs/Filters.js
index d2cce4f..a35ece2 100644
--- a/frontend/src/components/dialogs/Filters.js
+++ b/frontend/src/components/dialogs/Filters.js
@@ -16,7 +16,7 @@
*/
import React, {Component} from 'react';
-import {Col, Container, Modal, Row} from "react-bootstrap";
+import {Modal} from "react-bootstrap";
import ButtonField from "../fields/ButtonField";
import './Filters.scss';
import {cleanNumber, validateIpAddress, validateMin, validatePort} from "../../utils";
diff --git a/frontend/src/components/fields/TagField.js b/frontend/src/components/fields/TagField.js
index f1a48bd..89445b6 100644
--- a/frontend/src/components/fields/TagField.js
+++ b/frontend/src/components/fields/TagField.js
@@ -26,50 +26,47 @@ const _ = require('lodash');
class TagField extends Component {
+ state = {};
+
constructor(props) {
super(props);
this.id = `field-${this.props.name || "noname"}-${randomClassName()}`;
}
- state = {
-
- };
-
onAddition = (tag) => {
if (typeof this.props.onChange === "function") {
- const tags = [].concat(this.wrappedTags(), tag);
- this.props.onChange(tags.map(t => t.name), true, tag); // true == addition
+ this.props.onChange([].concat(this.props.tags, tag), true, tag); // true == addition
}
};
onDelete = (i) => {
if (typeof this.props.onChange === "function") {
- const tags = this.wrappedTags();
+ const tags = _.clone(this.props.tags);
const tag = tags[i];
tags.splice(i, 1);
- this.props.onChange(tags.map(t => t.name), true, tag); // false == delete
+ this.props.onChange(tags, true, tag); // false == delete
}
};
- wrappedTags = () => this.props.tags.map(t => new Object({"name": t}));
render() {
const small = this.props.small || false;
const name = this.props.name || null;
return (
-
- { name &&
+
+ {name &&
{name}:
}
-
+
+
+
);
}
diff --git a/frontend/src/components/fields/TagField.scss b/frontend/src/components/fields/TagField.scss
index e77db97..737f11f 100644
--- a/frontend/src/components/fields/TagField.scss
+++ b/frontend/src/components/fields/TagField.scss
@@ -1,31 +1,68 @@
@import "../../colors.scss";
.tag-field {
+ font-size: 0.9em;
+ margin: 5px 0;
+
+ .field-name {
+ label {
+ margin: 0;
+ }
+ }
+
+ &.field-small {
+ font-size: 0.8em;
+ }
+
+ &.field-inline {
+ display: flex;
+
+ .field-name {
+ padding: 6px 0 6px 7px;
+ border-top-left-radius: 5px;
+ border-bottom-left-radius: 5px;
+ background-color: $color-primary-2;
+ }
+
+ .field-input {
+ flex: 1;
+
+ .react-tags {
+ padding-left: 3px;
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ }
+ }
+
+ &:focus-within .field-name {
+ background-color: $color-primary-1;
+ }
+ }
+
.react-tags {
- font-size: 12px;
position: relative;
- z-index: 10;
- padding: 0 6px;
- cursor: text;
+ display: flex;
border-radius: 4px;
background-color: $color-primary-2;
- }
- .react-tags.is-focused {
- border-color: #b1b1b1;
+ &:focus-within,
+ &:focus-within .react-tags__search-input {
+ background-color: $color-primary-1;
+ }
}
.react-tags__selected {
- display: inline;
+ display: inline-block;
+ flex: 0 1;
+ margin: 6px 0;
+ white-space: nowrap;
}
.react-tags__selected-tag {
- font-size: 11px;
- display: inline-block;
- margin: 0 6px 6px 0;
+ font-size: 0.75em;
+ margin: 0 3px;
padding: 2px 4px;
color: $color-primary-3;
- border: none;
border-radius: 2px;
background: $color-primary-4;
}
@@ -39,12 +76,15 @@
.react-tags__selected-tag:hover,
.react-tags__selected-tag:focus {
border-color: #b1b1b1;
+ background-color: $color-primary-0;
+
+ &::after {
+ color: $color-primary-4;
+ }
}
.react-tags__search {
- display: inline-block;
- max-width: 100%;
- padding: 7px 10px;
+ flex: 1 0;
}
@media screen and (min-width: 30em) {
@@ -54,14 +94,7 @@
}
.react-tags__search-input {
- font-size: inherit;
- line-height: inherit;
- max-width: 100%;
- margin: 0;
- padding: 0;
color: $color-primary-4;
- border: 0;
- outline: none;
background-color: $color-primary-2;
}
@@ -71,6 +104,7 @@
.react-tags__suggestions {
position: absolute;
+ z-index: 50;
top: 100%;
left: 0;
width: 100%;
@@ -87,30 +121,33 @@
margin: 4px -1px;
padding: 0;
list-style: none;
- color: $color-primary-1;
- border-radius: 2px;
- background: $color-primary-4;
+ border-radius: 3px;
+ background: $color-primary-2;
}
.react-tags__suggestions li {
- padding: 3px 5px;
- border-bottom: 1px solid #ddd;
+ padding: 5px 10px;
}
.react-tags__suggestions li mark {
font-weight: 600;
- text-decoration: underline;
+ padding: 0;
+ color: $color-primary-4;
background: none;
}
.react-tags__suggestions li:hover {
cursor: pointer;
- color: $color-primary-4;
- background: $color-primary-0;
+ border-radius: 3px;
+ background: $color-primary-1;
+
+ mark {
+ color: $color-primary-4;
+ }
}
.react-tags__suggestions li.is-active {
- background: #b7cfe0;
+ background: $color-primary-3;
}
.react-tags__suggestions li.is-disabled {
diff --git a/frontend/src/components/fields/common.scss b/frontend/src/components/fields/common.scss
index 8fbef0d..e5dc65c 100644
--- a/frontend/src/components/fields/common.scss
+++ b/frontend/src/components/fields/common.scss
@@ -3,6 +3,7 @@
.field {
input,
textarea {
+ font-family: "Fira Code", monospace;
width: 100%;
padding: 7px 10px;
color: $color-primary-4;
diff --git a/frontend/src/components/filters/RulesConnectionsFilter.js b/frontend/src/components/filters/RulesConnectionsFilter.js
index 4c993dc..8e40d30 100644
--- a/frontend/src/components/filters/RulesConnectionsFilter.js
+++ b/frontend/src/components/filters/RulesConnectionsFilter.js
@@ -17,10 +17,9 @@
import React, {Component} from 'react';
import {withRouter} from "react-router-dom";
-import './RulesConnectionsFilter.scss';
-import ReactTags from 'react-tag-autocomplete';
import backend from "../../backend";
import dispatcher from "../../dispatcher";
+import TagField from "../fields/TagField";
const classNames = require('classnames');
const _ = require('lodash');
@@ -59,16 +58,8 @@ class RulesConnectionsFilter extends Component {
dispatcher.unregister(this.connectionsFiltersCallback);
}
- onDelete = (i) => {
- const activeRules = _.clone(this.state.activeRules);
- activeRules.splice(i, 1);
- this.setState({activeRules});
- dispatcher.dispatch("connections_filters", {"matched_rules": activeRules.map(r => r.id)});
- };
-
- onAddition = (rule) => {
- if (!this.state.activeRules.includes(rule)) {
- const activeRules = [].concat(this.state.activeRules, rule);
+ onChange = (activeRules) => {
+ if (!_.isEqual(activeRules.sort(), this.state.activeRules.sort())) {
this.setState({activeRules});
dispatcher.dispatch("connections_filters", {"matched_rules": activeRules.map(r => r.id)});
}
@@ -79,11 +70,9 @@ class RulesConnectionsFilter extends Component {
-
- suggestion.name.startsWith(query) && !this.state.activeRules.includes(suggestion)}/>
+
);
diff --git a/frontend/src/components/filters/RulesConnectionsFilter.scss b/frontend/src/components/filters/RulesConnectionsFilter.scss
deleted file mode 100644
index 0bb4952..0000000
--- a/frontend/src/components/filters/RulesConnectionsFilter.scss
+++ /dev/null
@@ -1,121 +0,0 @@
-@import "../../colors";
-
-.filter-rules {
- .react-tags {
- font-size: 12px;
- position: relative;
- z-index: 10;
- padding: 0 6px;
- cursor: text;
- border-radius: 4px;
- background-color: $color-primary-2;
- }
-
- .react-tags.is-focused {
- border-color: #b1b1b1;
- }
-
- .react-tags__selected {
- display: inline;
- }
-
- .react-tags__selected-tag {
- font-size: 11px;
- display: inline-block;
- margin: 0 6px 6px 0;
- padding: 2px 4px;
- color: $color-primary-3;
- border: none;
- border-radius: 2px;
- background: $color-primary-4;
- }
-
- .react-tags__selected-tag::after {
- margin-left: 8px;
- content: "\2715";
- color: $color-primary-3;
- }
-
- .react-tags__selected-tag:hover,
- .react-tags__selected-tag:focus {
- border-color: #b1b1b1;
- }
-
- .react-tags__search {
- display: inline-block;
- max-width: 100%;
- padding: 7px 10px;
- }
-
- @media screen and (min-width: 30em) {
- .react-tags__search {
- position: relative;
- }
- }
-
- .react-tags__search-input {
- font-size: inherit;
- line-height: inherit;
- max-width: 100%;
- margin: 0;
- padding: 0;
- color: $color-primary-4;
- border: 0;
- outline: none;
- background-color: $color-primary-2;
- }
-
- .react-tags__search-input::-ms-clear {
- display: none;
- }
-
- .react-tags__suggestions {
- position: absolute;
- top: 100%;
- left: 0;
- width: 100%;
- }
-
- @media screen and (min-width: 30em) {
- .react-tags__suggestions {
- width: 240px;
- }
- }
-
- .react-tags__suggestions ul {
- font-size: 12px;
- margin: 4px -1px;
- padding: 0;
- list-style: none;
- color: $color-primary-1;
- border-radius: 2px;
- background: $color-primary-4;
- }
-
- .react-tags__suggestions li {
- padding: 3px 5px;
- border-bottom: 1px solid #ddd;
- }
-
- .react-tags__suggestions li mark {
- font-weight: 600;
- text-decoration: underline;
- background: none;
- }
-
- .react-tags__suggestions li:hover {
- cursor: pointer;
- color: $color-primary-4;
- background: $color-primary-0;
- }
-
- .react-tags__suggestions li.is-active {
- background: #b7cfe0;
- }
-
- .react-tags__suggestions li.is-disabled {
- cursor: auto;
- opacity: 0.5;
- }
-
-}
diff --git a/frontend/src/components/objects/MessageAction.js b/frontend/src/components/objects/MessageAction.js
index 9f199b7..2b46320 100644
--- a/frontend/src/components/objects/MessageAction.js
+++ b/frontend/src/components/objects/MessageAction.js
@@ -43,7 +43,7 @@ class MessageAction extends Component {
return (
-
+
{s["id"].substring(0, 8)}
{dateTimeToTime(s["started_at"])}
{durationBetween(s["started_at"], s["completed_at"])}
@@ -166,13 +166,13 @@ class PcapsPane extends Component {
});
};
- const uploadCurlCommand = createCurlCommand("pcap/upload", "POST", null, {
+ const uploadCurlCommand = createCurlCommand("/pcap/upload", "POST", null, {
file: "@" + ((this.state.uploadSelectedFile != null && this.state.isUploadFileValid) ?
this.state.uploadSelectedFile.name : "invalid.pcap"),
flush_all: this.state.uploadFlushAll
});
- const fileCurlCommand = createCurlCommand("pcap/file", "POST", {
+ const fileCurlCommand = createCurlCommand("/pcap/file", "POST", {
file: this.state.fileValue,
flush_all: this.state.processFlushAll,
delete_original_file: this.state.deleteOriginalFile
diff --git a/frontend/src/components/panels/SearchPane.js b/frontend/src/components/panels/SearchPane.js
index 21ba139..1fb48ef 100644
--- a/frontend/src/components/panels/SearchPane.js
+++ b/frontend/src/components/panels/SearchPane.js
@@ -29,7 +29,6 @@ import dispatcher from "../../dispatcher";
import TagField from "../fields/TagField";
import CheckField from "../fields/CheckField";
-const classNames = require('classnames');
const _ = require('lodash');
class SearchPane extends Component {
@@ -161,7 +160,7 @@ class SearchPane extends Component {
const options = this.state.currentSearchOptions;
let searches = this.state.searches.map(s =>
-
+
{s.id.substring(0, 8)}
{this.extractPattern(s["search_options"])}
{s["affected_connections_count"]}
@@ -223,13 +222,14 @@ class SearchPane extends Component {
-
{return {name: t};})}
+ name="terms" min={3} inline allowNew={true}
readonly={regexOptionsModified || options.text_search.exact_phrase}
- onChange={(tags) => this.updateParam(s => s.text_search.terms = tags)}/>
- this.updateParam(s => s.text_search.terms = tags.map(t => t.name))}/>
+ {return {name: t};})}
+ name="excluded_terms" min={3} inline allowNew={true}
readonly={regexOptionsModified || options.text_search.exact_phrase}
- onChange={(tags) => this.updateParam(s => s.text_search.excluded_terms = tags)}/>
+ onChange={(tags) => this.updateParam(s => s.text_search.excluded_terms = tags.map(t => t.name))}/>
or
diff --git a/frontend/src/components/panels/StreamsPane.js b/frontend/src/components/panels/StreamsPane.js
index 1aa5c53..be39777 100644
--- a/frontend/src/components/panels/StreamsPane.js
+++ b/frontend/src/components/panels/StreamsPane.js
@@ -65,7 +65,7 @@ class StreamsPane extends Component {
}
loadStream = (connectionId) => {
- this.setState({messages: []});
+ this.setState({messages: [], currentId: connectionId});
backend.get(`/api/streams/${connectionId}?format=${this.state.format}`)
.then(res => this.setState({messages: res.json}));
};
diff --git a/statistics_controller.go b/statistics_controller.go
index fda7494..29f3fec 100644
--- a/statistics_controller.go
+++ b/statistics_controller.go
@@ -31,14 +31,14 @@ type StatisticRecord struct {
ServerBytesPerService map[uint16]int `json:"server_bytes_per_service" bson:"server_bytes_per_service"`
TotalBytesPerService map[uint16]int `json:"total_bytes_per_service" bson:"total_bytes_per_service"`
DurationPerService map[uint16]int64 `json:"duration_per_service" bson:"duration_per_service"`
- MatchedRules map[string]int64 `json:"matched_rules" bson:"matched_rules"`
+ MatchedRules map[string]int64 `json:"matched_rules" bson:"matched_rules"`
}
type StatisticsFilter struct {
RangeFrom time.Time `form:"range_from"`
RangeTo time.Time `form:"range_to"`
Ports []uint16 `form:"ports"`
- RulesIDs []string `form:"rules_ids"`
+ RulesIDs []string `form:"rules_ids"`
Metric string `form:"metric"`
}
@@ -91,7 +91,6 @@ func (sc *StatisticsController) GetStatistics(context context.Context, filter St
}
}
- log.Println(query)
if err := query.All(&statisticRecords); err != nil {
log.WithError(err).WithField("filter", filter).Error("failed to retrieve statistics")
return []StatisticRecord{}
--
cgit v1.2.3-70-g09d2
From 2fb8993008752063fa13f253784e9e92552e339d Mon Sep 17 00:00:00 2001
From: Emiliano Ciavatta
Date: Fri, 16 Oct 2020 11:13:21 +0200
Subject: Refactor js files
---
frontend/src/components/Header.js | 18 ++--
frontend/src/components/Notifications.js | 8 +-
frontend/src/components/Timeline.js | 30 +++---
frontend/src/components/dialogs/Filters.js | 6 +-
frontend/src/components/fields/ButtonField.js | 8 +-
frontend/src/components/fields/CheckField.js | 12 +--
frontend/src/components/fields/InputField.js | 20 ++--
frontend/src/components/fields/TagField.js | 12 +--
frontend/src/components/fields/TextField.js | 10 +-
.../src/components/fields/extensions/ColorField.js | 17 ++--
.../components/fields/extensions/NumericField.js | 6 +-
frontend/src/components/filters/AdvancedFilters.js | 4 +-
.../components/filters/BooleanConnectionsFilter.js | 6 +-
.../src/components/filters/ExitSearchFilter.js | 4 +-
.../components/filters/RulesConnectionsFilter.js | 6 +-
.../components/filters/StringConnectionsFilter.js | 20 +---
frontend/src/components/objects/Connection.js | 12 +--
frontend/src/components/objects/LinkPopover.js | 15 +--
frontend/src/components/pages/ConfigurationPage.js | 43 ++++-----
frontend/src/components/pages/MainPage.js | 20 ++--
.../src/components/pages/ServiceUnavailablePage.js | 4 +-
frontend/src/components/panels/ConnectionsPane.js | 4 +-
frontend/src/components/panels/MainPane.js | 12 +--
frontend/src/components/panels/PcapsPane.js | 34 +++----
frontend/src/components/panels/SearchPane.js | 106 +++++++++++----------
frontend/src/components/panels/StreamsPane.js | 18 ++--
frontend/src/dispatcher.js | 2 +-
frontend/src/index.js | 20 ++--
frontend/src/notifications.js | 2 +-
frontend/src/serviceWorker.js | 12 +--
frontend/src/setupProxy.js | 10 +-
frontend/src/setupTests.js | 2 +-
frontend/src/utils.js | 20 ++--
33 files changed, 258 insertions(+), 265 deletions(-)
(limited to 'frontend/src/components/objects')
diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js
index 119958b..ab16dd1 100644
--- a/frontend/src/components/Header.js
+++ b/frontend/src/components/Header.js
@@ -15,17 +15,17 @@
* along with this program. If not, see .
*/
-import React, {Component} from 'react';
-import Typed from 'typed.js';
-import './Header.scss';
+import React, {Component} from "react";
import {Link, withRouter} from "react-router-dom";
+import Typed from "typed.js";
+import {cleanNumber, validatePort} from "../utils";
import ButtonField from "./fields/ButtonField";
+import AdvancedFilters from "./filters/AdvancedFilters";
+import BooleanConnectionsFilter from "./filters/BooleanConnectionsFilter";
import ExitSearchFilter from "./filters/ExitSearchFilter";
-import {cleanNumber, validatePort} from "../utils";
-import StringConnectionsFilter from "./filters/StringConnectionsFilter";
import RulesConnectionsFilter from "./filters/RulesConnectionsFilter";
-import BooleanConnectionsFilter from "./filters/BooleanConnectionsFilter";
-import AdvancedFilters from "./filters/AdvancedFilters";
+import StringConnectionsFilter from "./filters/StringConnectionsFilter";
+import "./Header.scss";
class Header extends Component {
@@ -49,7 +49,7 @@ class Header extends Component {
- {
+ {
this.el = el;
}}/>
@@ -67,7 +67,7 @@ class Header extends Component {
-
+
diff --git a/frontend/src/components/Notifications.js b/frontend/src/components/Notifications.js
index ad681a2..56a4508 100644
--- a/frontend/src/components/Notifications.js
+++ b/frontend/src/components/Notifications.js
@@ -15,12 +15,12 @@
* along with this program. If not, see
.
*/
-import React, {Component} from 'react';
-import './Notifications.scss';
+import React, {Component} from "react";
import dispatcher from "../dispatcher";
+import "./Notifications.scss";
-const _ = require('lodash');
-const classNames = require('classnames');
+const _ = require("lodash");
+const classNames = require("classnames");
class Notifications extends Component {
diff --git a/frontend/src/components/Timeline.js b/frontend/src/components/Timeline.js
index 1d88bcb..8d1fd40 100644
--- a/frontend/src/components/Timeline.js
+++ b/frontend/src/components/Timeline.js
@@ -62,7 +62,7 @@ class Timeline extends Component {
this.loadStatistics(this.state.metric).then(() => log.debug("Statistics loaded after mount"));
- this.connectionsFiltersCallback = payload => {
+ 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"));
@@ -74,22 +74,22 @@ class Timeline extends Component {
};
dispatcher.register("connections_filters", this.connectionsFiltersCallback);
- dispatcher.register("connection_updates", payload => {
+ dispatcher.register("connection_updates", (payload) => {
this.setState({
selection: new TimeRange(payload.from, payload.to),
});
this.adjustSelection();
});
- dispatcher.register("notifications", payload => {
+ dispatcher.register("notifications", (payload) => {
if (payload.event === "services.edit" && this.state.metric !== "matched_rules") {
- this.loadServices().then(() => log.debug("Statistics reloaded after services updates"));
+ 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.loadServices().then(() => log.debug("Statistics reloaded after rules updates"));
+ this.loadStatistics(this.state.metric).then(() => log.debug("Statistics reloaded after rules updates"));
}
});
- dispatcher.register("pulse_timeline", payload => {
+ dispatcher.register("pulse_timeline", (payload) => {
this.setState({pulseTimeline: true});
setTimeout(() => this.setState({pulseTimeline: false}), payload.duration);
});
@@ -107,12 +107,12 @@ class Timeline extends Component {
if (metric === "matched_rules") {
let rules = await this.loadRules();
if (this.state.matchedRulesFilter.length > 0) {
- this.state.matchedRulesFilter.forEach(id => {
+ this.state.matchedRulesFilter.forEach((id) => {
urlParams.append("rules_ids", id);
});
columns = this.state.matchedRulesFilter;
} else {
- columns = rules.map(r => r.id);
+ columns = rules.map((r) => r.id);
}
} else {
let services = await this.loadServices();
@@ -124,7 +124,7 @@ class Timeline extends Component {
}
columns = Object.keys(services);
- columns.forEach(port => urlParams.append("ports", port));
+ columns.forEach((port) => urlParams.append("ports", port));
}
const metrics = (await backend.get("/api/statistics?" + urlParams)).json;
@@ -133,7 +133,7 @@ class Timeline extends Component {
}
const zeroFilledMetrics = [];
- const toTime = m => new Date(m["range_start"]).getTime();
+ const toTime = (m) => new Date(m["range_start"]).getTime();
let i = 0;
for (let interval = toTime(metrics[0]) - minutes; interval <= toTime(metrics[metrics.length - 1]) + minutes; interval += minutes) {
if (i < metrics.length && interval === toTime(metrics[i])) {
@@ -144,7 +144,7 @@ class Timeline extends Component {
const m = {};
m["range_start"] = new Date(interval);
m[metric] = {};
- columns.forEach(c => m[metric][c] = 0);
+ columns.forEach((c) => m[metric][c] = 0);
zeroFilledMetrics.push(m);
}
}
@@ -152,7 +152,7 @@ class Timeline extends Component {
const series = new TimeSeries({
name: "statistics",
columns: ["time"].concat(columns),
- points: zeroFilledMetrics.map(m => [m["range_start"]].concat(columns.map(c =>
+ points: zeroFilledMetrics.map((m) => [m["range_start"]].concat(columns.map(c =>
((metric in m) && (m[metric] != null)) ? (m[metric][c] || 0) : 0
)))
});
@@ -184,11 +184,11 @@ class Timeline extends Component {
createStyler = () => {
if (this.state.metric === "matched_rules") {
- return styler(this.state.rules.map(rule => {
+ return styler(this.state.rules.map((rule) => {
return {key: rule.id, color: rule.color, width: 2};
}));
} else {
- return styler(Object.keys(this.state.services).map(port => {
+ return styler(Object.keys(this.state.services).map((port) => {
return {key: port, color: this.state.services[port].color, width: 2};
}));
}
@@ -227,7 +227,7 @@ class Timeline extends Component {
};
aggregateSeries = (func) => {
- const values = this.state.series.columns().map(c => this.state.series[func](c));
+ const values = this.state.series.columns().map((c) => this.state.series[func](c));
return Math[func](...values);
};
diff --git a/frontend/src/components/dialogs/Filters.js b/frontend/src/components/dialogs/Filters.js
index a35ece2..a2407df 100644
--- a/frontend/src/components/dialogs/Filters.js
+++ b/frontend/src/components/dialogs/Filters.js
@@ -15,12 +15,12 @@
* along with this program. If not, see
.
*/
-import React, {Component} from 'react';
+import React, {Component} from "react";
import {Modal} from "react-bootstrap";
-import ButtonField from "../fields/ButtonField";
-import './Filters.scss';
import {cleanNumber, validateIpAddress, validateMin, validatePort} from "../../utils";
+import ButtonField from "../fields/ButtonField";
import StringConnectionsFilter from "../filters/StringConnectionsFilter";
+import "./Filters.scss";
class Filters extends Component {
diff --git a/frontend/src/components/fields/ButtonField.js b/frontend/src/components/fields/ButtonField.js
index 850f837..de747a5 100644
--- a/frontend/src/components/fields/ButtonField.js
+++ b/frontend/src/components/fields/ButtonField.js
@@ -15,11 +15,11 @@
* along with this program. If not, see
.
*/
-import React, {Component} from 'react';
-import './ButtonField.scss';
-import './common.scss';
+import React, {Component} from "react";
+import "./ButtonField.scss";
+import "./common.scss";
-const classNames = require('classnames');
+const classNames = require("classnames");
class ButtonField extends Component {
diff --git a/frontend/src/components/fields/CheckField.js b/frontend/src/components/fields/CheckField.js
index a0e2706..bfa1c9d 100644
--- a/frontend/src/components/fields/CheckField.js
+++ b/frontend/src/components/fields/CheckField.js
@@ -15,12 +15,12 @@
* along with this program. If not, see
.
*/
-import React, {Component} from 'react';
-import './CheckField.scss';
-import './common.scss';
+import React, {Component} from "react";
import {randomClassName} from "../../utils";
+import "./CheckField.scss";
+import "./common.scss";
-const classNames = require('classnames');
+const classNames = require("classnames");
class CheckField extends Component {
@@ -41,9 +41,9 @@ class CheckField extends Component {
};
return (
-
+
diff --git a/frontend/src/components/fields/InputField.js b/frontend/src/components/fields/InputField.js
index 80cce3b..823989d 100644
--- a/frontend/src/components/fields/InputField.js
+++ b/frontend/src/components/fields/InputField.js
@@ -15,12 +15,12 @@
* along with this program. If not, see
.
*/
-import React, {Component} from 'react';
-import './InputField.scss';
-import './common.scss';
+import React, {Component} from "react";
import {randomClassName} from "../../utils";
+import "./common.scss";
+import "./InputField.scss";
-const classNames = require('classnames');
+const classNames = require("classnames");
class InputField extends Component {
@@ -59,23 +59,23 @@ class InputField extends Component {
}
return (
-
- { name &&
+ {name &&
{name}:
}
- { type === "file" &&
- {value.name || this.props.placeholder} }
+ {type === "file" &&
+ {value.name || this.props.placeholder} }
+ readOnly={this.props.readonly}/>
- { type !== "file" && value !== "" &&
+ {type !== "file" && value !== "" &&
handler(null)}>del
diff --git a/frontend/src/components/fields/TagField.js b/frontend/src/components/fields/TagField.js
index 89445b6..9a36da4 100644
--- a/frontend/src/components/fields/TagField.js
+++ b/frontend/src/components/fields/TagField.js
@@ -15,14 +15,14 @@
* along with this program. If not, see
.
*/
-import React, {Component} from 'react';
-import './TagField.scss';
-import './common.scss';
-import {randomClassName} from "../../utils";
+import React, {Component} from "react";
import ReactTags from "react-tag-autocomplete";
+import {randomClassName} from "../../utils";
+import "./common.scss";
+import "./TagField.scss";
-const classNames = require('classnames');
-const _ = require('lodash');
+const classNames = require("classnames");
+const _ = require("lodash");
class TagField extends Component {
diff --git a/frontend/src/components/fields/TextField.js b/frontend/src/components/fields/TextField.js
index 9237c0c..4dd77bd 100644
--- a/frontend/src/components/fields/TextField.js
+++ b/frontend/src/components/fields/TextField.js
@@ -15,12 +15,12 @@
* along with this program. If not, see
.
*/
-import React, {Component} from 'react';
-import './TextField.scss';
-import './common.scss';
+import React, {Component} from "react";
import {randomClassName} from "../../utils";
+import "./common.scss";
+import "./TextField.scss";
-const classNames = require('classnames');
+const classNames = require("classnames");
class TextField extends Component {
@@ -50,7 +50,7 @@ class TextField extends Component {
{"field-invalid": this.props.invalid}, {"field-small": this.props.small})}>
{name &&
{name}: }
+ readOnly={this.props.readonly} value={this.props.value} ref={this.props.textRef}/>
{error &&
error: {error}
}
);
diff --git a/frontend/src/components/fields/extensions/ColorField.js b/frontend/src/components/fields/extensions/ColorField.js
index f1c0caf..fd30988 100644
--- a/frontend/src/components/fields/extensions/ColorField.js
+++ b/frontend/src/components/fields/extensions/ColorField.js
@@ -15,22 +15,21 @@
* along with this program. If not, see
.
*/
-import React, {Component} from 'react';
+import React, {Component} from "react";
import {OverlayTrigger, Popover} from "react-bootstrap";
-import './ColorField.scss';
-import InputField from "../InputField";
import validation from "../../../validation";
+import InputField from "../InputField";
+import "./ColorField.scss";
class ColorField extends Component {
constructor(props) {
super(props);
- this.state = {
- };
+ this.state = {};
- this.colors = ["#E53935", "#D81B60", "#8E24AA", "#5E35B1", "#3949AB", "#1E88E5", "#039BE5", "#00ACC1",
- "#00897B", "#43A047", "#7CB342", "#9E9D24", "#F9A825", "#FB8C00", "#F4511E", "#6D4C41"];
+ this.colors = ["#e53935", "#d81b60", "#8e24aa", "#5e35b1", "#3949ab", "#1e88e5", "#039be5", "#00acc1",
+ "#00897b", "#43a047", "#7cb342", "#9e9d24", "#f9a825", "#fb8c00", "#f4511e", "#6d4c41"];
}
componentDidUpdate(prevProps, prevState, snapshot) {
@@ -55,7 +54,7 @@ class ColorField extends Component {
this.props.onChange(color);
}
document.body.click(); // magic to close popup
- }} />);
+ }}/>);
const popover = (
@@ -82,7 +81,7 @@ class ColorField extends Component {
+ error={null}/>
pick
diff --git a/frontend/src/components/fields/extensions/NumericField.js b/frontend/src/components/fields/extensions/NumericField.js
index d4d027d..a6cba26 100644
--- a/frontend/src/components/fields/extensions/NumericField.js
+++ b/frontend/src/components/fields/extensions/NumericField.js
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-import React, {Component} from 'react';
+import React, {Component} from "react";
import InputField from "../InputField";
class NumericField extends Component {
@@ -35,7 +35,7 @@ class NumericField extends Component {
}
onChange = (value) => {
- value = value.toString().replace(/[^\d]/gi, '');
+ value = value.toString().replace(/[^\d]/gi, "");
let intValue = 0;
if (value !== "") {
intValue = parseInt(value, 10);
@@ -53,7 +53,7 @@ class NumericField extends Component {
render() {
return (
+ invalid={this.state.invalid}/>
);
}
diff --git a/frontend/src/components/filters/AdvancedFilters.js b/frontend/src/components/filters/AdvancedFilters.js
index f5ba825..2b479ed 100644
--- a/frontend/src/components/filters/AdvancedFilters.js
+++ b/frontend/src/components/filters/AdvancedFilters.js
@@ -15,11 +15,11 @@
* along with this program. If not, see .
*/
-import React, {Component} from 'react';
+import React, {Component} from "react";
import {withRouter} from "react-router-dom";
import dispatcher from "../../dispatcher";
-import ButtonField from "../fields/ButtonField";
import {updateParams} from "../../utils";
+import ButtonField from "../fields/ButtonField";
class AdvancedFilters extends Component {
diff --git a/frontend/src/components/filters/BooleanConnectionsFilter.js b/frontend/src/components/filters/BooleanConnectionsFilter.js
index 9558323..0355167 100644
--- a/frontend/src/components/filters/BooleanConnectionsFilter.js
+++ b/frontend/src/components/filters/BooleanConnectionsFilter.js
@@ -15,10 +15,10 @@
* along with this program. If not, see .
*/
-import React, {Component} from 'react';
+import React, {Component} from "react";
import {withRouter} from "react-router-dom";
-import CheckField from "../fields/CheckField";
import dispatcher from "../../dispatcher";
+import CheckField from "../fields/CheckField";
class BooleanConnectionsFilter extends Component {
@@ -30,7 +30,7 @@ class BooleanConnectionsFilter extends Component {
let params = new URLSearchParams(this.props.location.search);
this.setState({filterActive: this.toBoolean(params.get(this.props.filterName)).toString()});
- this.connectionsFiltersCallback = payload => {
+ this.connectionsFiltersCallback = (payload) => {
const name = this.props.filterName;
if (name in payload && this.state.filterActive !== payload[name]) {
this.setState({filterActive: payload[name]});
diff --git a/frontend/src/components/filters/ExitSearchFilter.js b/frontend/src/components/filters/ExitSearchFilter.js
index 68ca686..72cfb0c 100644
--- a/frontend/src/components/filters/ExitSearchFilter.js
+++ b/frontend/src/components/filters/ExitSearchFilter.js
@@ -15,10 +15,10 @@
* along with this program. If not, see .
*/
-import React, {Component} from 'react';
+import React, {Component} from "react";
import {withRouter} from "react-router-dom";
-import CheckField from "../fields/CheckField";
import dispatcher from "../../dispatcher";
+import CheckField from "../fields/CheckField";
class ExitSearchFilter extends Component {
diff --git a/frontend/src/components/filters/RulesConnectionsFilter.js b/frontend/src/components/filters/RulesConnectionsFilter.js
index 8e40d30..86eae7e 100644
--- a/frontend/src/components/filters/RulesConnectionsFilter.js
+++ b/frontend/src/components/filters/RulesConnectionsFilter.js
@@ -15,14 +15,14 @@
* along with this program. If not, see .
*/
-import React, {Component} from 'react';
+import React, {Component} from "react";
import {withRouter} from "react-router-dom";
import backend from "../../backend";
import dispatcher from "../../dispatcher";
import TagField from "../fields/TagField";
-const classNames = require('classnames');
-const _ = require('lodash');
+const classNames = require("classnames");
+const _ = require("lodash");
class RulesConnectionsFilter extends Component {
diff --git a/frontend/src/components/filters/StringConnectionsFilter.js b/frontend/src/components/filters/StringConnectionsFilter.js
index 18b3784..c3c5925 100644
--- a/frontend/src/components/filters/StringConnectionsFilter.js
+++ b/frontend/src/components/filters/StringConnectionsFilter.js
@@ -15,10 +15,10 @@
* along with this program. If not, see .
*/
-import React, {Component} from 'react';
+import React, {Component} from "react";
import {withRouter} from "react-router-dom";
-import InputField from "../fields/InputField";
import dispatcher from "../../dispatcher";
+import InputField from "../fields/InputField";
class StringConnectionsFilter extends Component {
@@ -56,15 +56,9 @@ class StringConnectionsFilter extends Component {
fieldValue = this.props.replaceFunc(fieldValue);
}
if (this.isValueValid(fieldValue)) {
- this.setState({
- fieldValue: fieldValue,
- filterValue: filterValue
- });
+ this.setState({fieldValue, filterValue: filterValue});
} else {
- this.setState({
- fieldValue: fieldValue,
- invalidValue: true
- });
+ this.setState({fieldValue, invalidValue: true});
}
} else {
this.setState({fieldValue: "", filterValue: null});
@@ -97,7 +91,6 @@ class StringConnectionsFilter extends Component {
}
-
if (this.isValueValid(fieldValue)) {
let filterValue = fieldValue;
if (filterValue !== "" && typeof this.props.encodeFunc === "function") {
@@ -113,10 +106,7 @@ class StringConnectionsFilter extends Component {
invalidValue: false
});
} else {
- this.setState({
- fieldValue: fieldValue,
- invalidValue: true
- });
+ this.setState({fieldValue, invalidValue: true});
}
};
diff --git a/frontend/src/components/objects/Connection.js b/frontend/src/components/objects/Connection.js
index 96f2235..f838606 100644
--- a/frontend/src/components/objects/Connection.js
+++ b/frontend/src/components/objects/Connection.js
@@ -15,17 +15,17 @@
* along with this program. If not, see .
*/
-import React, {Component} from 'react';
-import './Connection.scss';
+import React, {Component} from "react";
import {Form} from "react-bootstrap";
import backend from "../../backend";
+import dispatcher from "../../dispatcher";
import {dateTimeToTime, durationBetween, formatSize} from "../../utils";
import ButtonField from "../fields/ButtonField";
-import LinkPopover from "./LinkPopover";
import TextField from "../fields/TextField";
-import dispatcher from "../../dispatcher";
+import "./Connection.scss";
+import LinkPopover from "./LinkPopover";
-const classNames = require('classnames');
+const classNames = require("classnames");
class Connection extends Component {
@@ -59,7 +59,7 @@ class Connection extends Component {
}
if (name === "copy") {
this.copyTextarea.current.select();
- document.execCommand('copy');
+ document.execCommand("copy");
this.setState({copiedMessage: true});
setTimeout(() => this.setState({copiedMessage: false}), 3000);
}
diff --git a/frontend/src/components/objects/LinkPopover.js b/frontend/src/components/objects/LinkPopover.js
index 3c5bf67..551a819 100644
--- a/frontend/src/components/objects/LinkPopover.js
+++ b/frontend/src/components/objects/LinkPopover.js
@@ -15,10 +15,10 @@
* along with this program. If not, see .
*/
-import React, {Component} from 'react';
-import {randomClassName} from "../../utils";
+import React, {Component} from "react";
import {OverlayTrigger, Popover} from "react-bootstrap";
-import './LinkPopover.scss';
+import {randomClassName} from "../../utils";
+import "./LinkPopover.scss";
class LinkPopover extends Component {
@@ -39,10 +39,11 @@ class LinkPopover extends Component {
);
return (this.props.content ?
-
- {this.props.text}
- :
- {this.props.text}
+
+ {this.props.text}
+ :
+ {this.props.text}
);
}
}
diff --git a/frontend/src/components/pages/ConfigurationPage.js b/frontend/src/components/pages/ConfigurationPage.js
index 6ab8ae3..2bd2da7 100644
--- a/frontend/src/components/pages/ConfigurationPage.js
+++ b/frontend/src/components/pages/ConfigurationPage.js
@@ -15,19 +15,19 @@
* along with this program. If not, see .
*/
-import React, {Component} from 'react';
-import '../panels/common.scss';
-import './ConfigurationPage.scss';
-import LinkPopover from "../objects/LinkPopover";
+import React, {Component} from "react";
import {Col, Container, Row} from "react-bootstrap";
-import InputField from "../fields/InputField";
-import TextField from "../fields/TextField";
-import ButtonField from "../fields/ButtonField";
-import CheckField from "../fields/CheckField";
-import {createCurlCommand} from "../../utils";
import Table from "react-bootstrap/Table";
-import validation from "../../validation";
import backend from "../../backend";
+import {createCurlCommand} from "../../utils";
+import validation from "../../validation";
+import ButtonField from "../fields/ButtonField";
+import CheckField from "../fields/CheckField";
+import InputField from "../fields/InputField";
+import TextField from "../fields/TextField";
+import LinkPopover from "../objects/LinkPopover";
+import "../panels/common.scss";
+import "./ConfigurationPage.scss";
class ConfigurationPage extends Component {
@@ -40,8 +40,7 @@ class ConfigurationPage extends Component {
"flag_regex": "",
"auth_required": false
},
- "accounts": {
- }
+ "accounts": {}
},
newUsername: "",
newPassword: ""
@@ -50,9 +49,9 @@ class ConfigurationPage extends Component {
saveSettings = () => {
if (this.validateSettings(this.state.settings)) {
- backend.post("/setup", this.state.settings).then(_ => {
+ backend.post("/setup", this.state.settings).then((_) => {
this.props.onConfigured();
- }).catch(res => {
+ }).catch((res) => {
this.setState({setupStatusCode: res.status, setupResponse: JSON.stringify(res.json)});
});
}
@@ -102,14 +101,14 @@ class ConfigurationPage extends Component {
const accounts = Object.entries(settings.accounts).map(([username, password]) =>
{username}
-
+
this.updateParam((s) => delete s.accounts[username]) }/>
+ onClick={() => this.updateParam((s) => delete s.accounts[username])}/>
).concat(
this.setState({newUsername: v})} />
+ onChange={(v) => this.setState({newUsername: v})}/>
this.setState({newPassword: v})} />
+ onChange={(v) => this.setState({newPassword: v})}/>
);
@@ -122,7 +121,7 @@ class ConfigurationPage extends Component {
POST /setup
+ placement="left"/>
@@ -131,10 +130,10 @@ class ConfigurationPage extends Component {
this.updateParam((s) => s.config.server_address = v)} />
+ onChange={(v) => this.updateParam((s) => s.config.server_address = v)}/>
this.updateParam((s) => s.config.flag_regex = v)}
- error={this.state.flagRegexError} />
+ error={this.state.flagRegexError}/>
this.updateParam((s) => s.config.auth_required = v)}/>
@@ -166,7 +165,7 @@ class ConfigurationPage extends Component {
-
+
diff --git a/frontend/src/components/pages/MainPage.js b/frontend/src/components/pages/MainPage.js
index 0b06f55..a542e3f 100644
--- a/frontend/src/components/pages/MainPage.js
+++ b/frontend/src/components/pages/MainPage.js
@@ -15,20 +15,20 @@
* along with this program. If not, see
.
*/
-import React, {Component} from 'react';
-import './MainPage.scss';
-import './common.scss';
-import Connections from "../panels/ConnectionsPane";
-import StreamsPane from "../panels/StreamsPane";
+import React, {Component} from "react";
import {BrowserRouter as Router, Route, Switch} from "react-router-dom";
-import Timeline from "../Timeline";
-import PcapsPane from "../panels/PcapsPane";
-import RulesPane from "../panels/RulesPane";
-import ServicesPane from "../panels/ServicesPane";
-import Header from "../Header";
import Filters from "../dialogs/Filters";
+import Header from "../Header";
+import Connections from "../panels/ConnectionsPane";
import MainPane from "../panels/MainPane";
+import PcapsPane from "../panels/PcapsPane";
+import RulesPane from "../panels/RulesPane";
import SearchPane from "../panels/SearchPane";
+import ServicesPane from "../panels/ServicesPane";
+import StreamsPane from "../panels/StreamsPane";
+import Timeline from "../Timeline";
+import "./common.scss";
+import "./MainPage.scss";
class MainPage extends Component {
diff --git a/frontend/src/components/pages/ServiceUnavailablePage.js b/frontend/src/components/pages/ServiceUnavailablePage.js
index f27d84d..deb4cf8 100644
--- a/frontend/src/components/pages/ServiceUnavailablePage.js
+++ b/frontend/src/components/pages/ServiceUnavailablePage.js
@@ -15,8 +15,8 @@
* along with this program. If not, see
.
*/
-import React, {Component} from 'react';
-import './MainPage.scss';
+import React, {Component} from "react";
+import "./MainPage.scss";
class ServiceUnavailablePage extends Component {
diff --git a/frontend/src/components/panels/ConnectionsPane.js b/frontend/src/components/panels/ConnectionsPane.js
index 89859e6..ea47059 100644
--- a/frontend/src/components/panels/ConnectionsPane.js
+++ b/frontend/src/components/panels/ConnectionsPane.js
@@ -85,8 +85,8 @@ class ConnectionsPane extends Component {
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),
+ "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}`));
};
diff --git a/frontend/src/components/panels/MainPane.js b/frontend/src/components/panels/MainPane.js
index 8aa8ad8..ce72be5 100644
--- a/frontend/src/components/panels/MainPane.js
+++ b/frontend/src/components/panels/MainPane.js
@@ -15,15 +15,15 @@
* along with this program. If not, see
.
*/
-import React, {Component} from 'react';
-import './common.scss';
-import './MainPane.scss';
+import React, {Component} from "react";
import Typed from "typed.js";
import dispatcher from "../../dispatcher";
-import RulesPane from "./RulesPane";
-import StreamsPane from "./StreamsPane";
+import "./common.scss";
+import "./MainPane.scss";
import PcapsPane from "./PcapsPane";
+import RulesPane from "./RulesPane";
import ServicesPane from "./ServicesPane";
+import StreamsPane from "./StreamsPane";
class MainPane extends Component {
@@ -94,7 +94,7 @@ class MainPane extends Component {
- {
+ {
this.el = el;
}}/>
diff --git a/frontend/src/components/panels/PcapsPane.js b/frontend/src/components/panels/PcapsPane.js
index 900aacc..fd3db75 100644
--- a/frontend/src/components/panels/PcapsPane.js
+++ b/frontend/src/components/panels/PcapsPane.js
@@ -15,18 +15,18 @@
* along with this program. If not, see
.
*/
-import React, {Component} from 'react';
-import './PcapsPane.scss';
-import './common.scss';
+import React, {Component} from "react";
import Table from "react-bootstrap/Table";
import backend from "../../backend";
+import dispatcher from "../../dispatcher";
import {createCurlCommand, dateTimeToTime, durationBetween, formatSize} from "../../utils";
-import InputField from "../fields/InputField";
+import ButtonField from "../fields/ButtonField";
import CheckField from "../fields/CheckField";
+import InputField from "../fields/InputField";
import TextField from "../fields/TextField";
-import ButtonField from "../fields/ButtonField";
import LinkPopover from "../objects/LinkPopover";
-import dispatcher from "../../dispatcher";
+import "./common.scss";
+import "./PcapsPane.scss";
class PcapsPane extends Component {
@@ -45,7 +45,7 @@ class PcapsPane extends Component {
componentDidMount() {
this.loadSessions();
- dispatcher.register("notifications", payload => {
+ dispatcher.register("notifications", (payload) => {
if (payload.event === "pcap.upload" || payload.event === "pcap.file") {
this.loadSessions();
}
@@ -56,8 +56,8 @@ class PcapsPane extends Component {
loadSessions = () => {
backend.get("/api/pcap/sessions")
- .then(res => this.setState({sessions: res.json, sessionsStatusCode: res.status}))
- .catch(res => this.setState({
+ .then((res) => this.setState({sessions: res.json, sessionsStatusCode: res.status}))
+ .catch((res) => this.setState({
sessions: res.json, sessionsStatusCode: res.status,
sessionsResponse: JSON.stringify(res.json)
}));
@@ -72,14 +72,14 @@ class PcapsPane extends Component {
const formData = new FormData();
formData.append("file", this.state.uploadSelectedFile);
formData.append("flush_all", this.state.uploadFlushAll);
- backend.postFile("/api/pcap/upload", formData).then(res => {
+ backend.postFile("/api/pcap/upload", formData).then((res) => {
this.setState({
uploadStatusCode: res.status,
uploadResponse: JSON.stringify(res.json)
});
this.resetUpload();
this.loadSessions();
- }).catch(res => this.setState({
+ }).catch((res) => this.setState({
uploadStatusCode: res.status,
uploadResponse: JSON.stringify(res.json)
})
@@ -96,14 +96,14 @@ class PcapsPane extends Component {
file: this.state.fileValue,
flush_all: this.state.processFlushAll,
delete_original_file: this.state.deleteOriginalFile
- }).then(res => {
+ }).then((res) => {
this.setState({
processStatusCode: res.status,
processResponse: JSON.stringify(res.json)
});
this.resetProcess();
this.loadSessions();
- }).catch(res => this.setState({
+ }).catch((res) => this.setState({
processStatusCode: res.status,
processResponse: JSON.stringify(res.json)
})
@@ -130,7 +130,7 @@ class PcapsPane extends Component {
};
render() {
- let sessions = this.state.sessions.map(s =>
+ let sessions = this.state.sessions.map((s) =>
{s["id"].substring(0, 8)}
{dateTimeToTime(s["started_at"])}
@@ -229,7 +229,7 @@ class PcapsPane extends Component {
options:
this.setState({uploadFlushAll: v})}/>
+ onChange={(v) => this.setState({uploadFlushAll: v})}/>
@@ -254,9 +254,9 @@ class PcapsPane extends Component {
this.setState({processFlushAll: v})}/>
+ onChange={(v) => this.setState({processFlushAll: v})}/>
this.setState({deleteOriginalFile: v})}/>
+ onChange={(v) => this.setState({deleteOriginalFile: v})}/>
diff --git a/frontend/src/components/panels/SearchPane.js b/frontend/src/components/panels/SearchPane.js
index 1fb48ef..d3c0c8b 100644
--- a/frontend/src/components/panels/SearchPane.js
+++ b/frontend/src/components/panels/SearchPane.js
@@ -15,21 +15,21 @@
* along with this program. If not, see .
*/
-import React, {Component} from 'react';
-import './common.scss';
-import './SearchPane.scss';
+import React, {Component} from "react";
import Table from "react-bootstrap/Table";
-import InputField from "../fields/InputField";
-import TextField from "../fields/TextField";
import backend from "../../backend";
-import ButtonField from "../fields/ButtonField";
-import LinkPopover from "../objects/LinkPopover";
-import {createCurlCommand, dateTimeToTime, durationBetween} from "../../utils";
import dispatcher from "../../dispatcher";
-import TagField from "../fields/TagField";
+import {createCurlCommand, dateTimeToTime, durationBetween} from "../../utils";
+import ButtonField from "../fields/ButtonField";
import CheckField from "../fields/CheckField";
+import InputField from "../fields/InputField";
+import TagField from "../fields/TagField";
+import TextField from "../fields/TextField";
+import LinkPopover from "../objects/LinkPopover";
+import "./common.scss";
+import "./SearchPane.scss";
-const _ = require('lodash');
+const _ = require("lodash");
class SearchPane extends Component {
@@ -104,15 +104,15 @@ class SearchPane extends Component {
validateSearch = (options) => {
let valid = true;
- if (options.text_search.exact_phrase && options.text_search.exact_phrase.length < 3) {
+ if (options["text_search"]["exact_phrase"] && options["text_search"]["exact_phrase"].length < 3) {
this.setState({exactPhraseError: "text_search.exact_phrase.length < 3"});
valid = false;
}
- if (options.regex_search.pattern && options.regex_search.pattern.length < 3) {
+ if (options["regex_search"].pattern && options["regex_search"].pattern.length < 3) {
this.setState({patternError: "regex_search.pattern.length < 3"});
valid = false;
}
- if (options.regex_search.not_pattern && options.regex_search.not_pattern.length < 3) {
+ if (options["regex_search"]["not_pattern"] && options["regex_search"]["not_pattern"].length < 3) {
this.setState({exactPhraseError: "regex_search.not_pattern.length < 3"});
valid = false;
}
@@ -128,25 +128,25 @@ class SearchPane extends Component {
extractPattern = (options) => {
let pattern = "";
if (_.isEqual(options.regex_search, this.searchOptions.regex_search)) { // is text search
- if (options.text_search.exact_phrase) {
- pattern += `"${options.text_search.exact_phrase}"`;
+ if (options["text_search"]["exact_phrase"]) {
+ pattern += `"${options["text_search"]["exact_phrase"]}"`;
} else {
- pattern += options.text_search.terms.join(" ");
- if (options.text_search.excluded_terms) {
- pattern += " -" + options.text_search.excluded_terms.join(" -");
+ pattern += options["text_search"].terms.join(" ");
+ if (options["text_search"]["excluded_terms"]) {
+ pattern += " -" + options["text_search"]["excluded_terms"].join(" -");
}
}
- options.text_search.case_sensitive && (pattern += "/s");
+ options["text_search"]["case_sensitive"] && (pattern += "/s");
} else { // is regex search
- if (options.regex_search.pattern) {
- pattern += "/" + options.regex_search.pattern + "/";
+ if (options["regex_search"].pattern) {
+ pattern += "/" + options["regex_search"].pattern + "/";
} else {
- pattern += "!/" + options.regex_search.not_pattern + "/";
+ pattern += "!/" + options["regex_search"]["not_pattern"] + "/";
}
- options.regex_search.case_insensitive && (pattern += "i");
- options.regex_search.multi_line && (pattern += "m");
- options.regex_search.ignore_whitespaces && (pattern += "x");
- options.regex_search.dot_character && (pattern += "s");
+ options["regex_search"]["case_insensitive"] && (pattern += "i");
+ options["regex_search"]["multi_line"] && (pattern += "m");
+ options["regex_search"]["ignore_whitespaces"] && (pattern += "x");
+ options["regex_search"]["dot_character"] && (pattern += "s");
}
return pattern;
@@ -222,25 +222,29 @@ class SearchPane extends Component {
- {return {name: t};})}
+ {
+ return {name: t};
+ })}
name="terms" min={3} inline allowNew={true}
- readonly={regexOptionsModified || options.text_search.exact_phrase}
- onChange={(tags) => this.updateParam(s => s.text_search.terms = tags.map(t => t.name))}/>
- {return {name: t};})}
+ readonly={regexOptionsModified || options["text_search"]["exact_phrase"]}
+ onChange={(tags) => this.updateParam(s => s["text_search"].terms = tags.map(t => t.name))}/>
+ {
+ return {name: t};
+ })}
name="excluded_terms" min={3} inline allowNew={true}
- readonly={regexOptionsModified || options.text_search.exact_phrase}
- onChange={(tags) => this.updateParam(s => s.text_search.excluded_terms = tags.map(t => t.name))}/>
+ readonly={regexOptionsModified || options["text_search"]["exact_phrase"]}
+ onChange={(tags) => this.updateParam(s => s["text_search"]["excluded_terms"] = tags.map(t => t.name))}/>
or
- this.updateParam(s => s.text_search.exact_phrase = v)}
- readonly={regexOptionsModified || (Array.isArray(options.text_search.terms) && options.text_search.terms.length > 0)}/>
+ onChange={v => this.updateParam(s => s["text_search"]["exact_phrase"] = v)}
+ readonly={regexOptionsModified || (Array.isArray(options["text_search"].terms) && options["text_search"].terms.length > 0)}/>
- this.updateParam(s => s.text_search.case_sensitive = v)}/>
+ onChange={(v) => this.updateParam(s => s["text_search"]["case_sensitive"] = v)}/>
@@ -248,30 +252,30 @@ class SearchPane extends Component {
-
this.updateParam(s => s.regex_search.pattern = v)}/>
+ readonly={textOptionsModified || options["regex_search"]["not_pattern"]}
+ onChange={v => this.updateParam(s => s["regex_search"].pattern = v)}/>
or
- this.updateParam(s => s.regex_search.not_pattern = v)}/>
+ readonly={textOptionsModified || options["regex_search"].pattern}
+ onChange={v => this.updateParam(s => s["regex_search"]["not_pattern"] = v)}/>
- this.updateParam(s => s.regex_search.case_insensitive = v)}/>
- this.updateParam(s => s["regex_search"]["case_insensitive"] = v)}/>
+ this.updateParam(s => s.regex_search.multi_line = v)}/>
- this.updateParam(s => s["regex_search"]["multi_line"] = v)}/>
+ this.updateParam(s => s.regex_search.ignore_whitespaces = v)}/>
- this.updateParam(s => s["regex_search"]["ignore_whitespaces"] = v)}/>
+ this.updateParam(s => s.regex_search.dot_character = v)}/>
+ onChange={(v) => this.updateParam(s => s["regex_search"]["dot_character"] = v)}/>
diff --git a/frontend/src/components/panels/StreamsPane.js b/frontend/src/components/panels/StreamsPane.js
index be39777..41ab33d 100644
--- a/frontend/src/components/panels/StreamsPane.js
+++ b/frontend/src/components/panels/StreamsPane.js
@@ -15,19 +15,19 @@
* along with this program. If not, see .
*/
-import React, {Component} from 'react';
-import './StreamsPane.scss';
-import {Row} from 'react-bootstrap';
-import MessageAction from "../objects/MessageAction";
+import DOMPurify from "dompurify";
+import React, {Component} from "react";
+import {Row} from "react-bootstrap";
+import ReactJson from "react-json-view"
import backend from "../../backend";
+import log from "../../log";
+import {downloadBlob, getHeaderValue} from "../../utils";
import ButtonField from "../fields/ButtonField";
import ChoiceField from "../fields/ChoiceField";
-import DOMPurify from 'dompurify';
-import ReactJson from 'react-json-view'
-import {downloadBlob, getHeaderValue} from "../../utils";
-import log from "../../log";
+import MessageAction from "../objects/MessageAction";
+import "./StreamsPane.scss";
-const classNames = require('classnames');
+const classNames = require("classnames");
class StreamsPane extends Component {
diff --git a/frontend/src/dispatcher.js b/frontend/src/dispatcher.js
index 881970f..ef5dbde 100644
--- a/frontend/src/dispatcher.js
+++ b/frontend/src/dispatcher.js
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-const _ = require('lodash');
+const _ = require("lodash");
class Dispatcher {
diff --git a/frontend/src/index.js b/frontend/src/index.js
index 4bc2730..d00df88 100644
--- a/frontend/src/index.js
+++ b/frontend/src/index.js
@@ -15,21 +15,21 @@
* along with this program. If not, see .
*/
-import React from 'react';
-import ReactDOM from 'react-dom';
-import 'bootstrap/dist/css/bootstrap.css';
-import './index.scss';
-import App from './components/App';
-import * as serviceWorker from './serviceWorker';
+import "bootstrap/dist/css/bootstrap.css";
+import React from "react";
+import ReactDOM from "react-dom";
+import App from "./components/App";
+import "./index.scss";
import notifications from "./notifications";
+import * as serviceWorker from "./serviceWorker";
notifications.createWebsocket();
ReactDOM.render(
- //
- ,
- // ,
- document.getElementById('root')
+ //
+ ,
+ // ,
+ document.getElementById("root")
);
serviceWorker.unregister();
diff --git a/frontend/src/notifications.js b/frontend/src/notifications.js
index f04036d..3c83b87 100644
--- a/frontend/src/notifications.js
+++ b/frontend/src/notifications.js
@@ -15,8 +15,8 @@
* along with this program. If not, see .
*/
-import log from "./log";
import dispatcher from "./dispatcher";
+import log from "./log";
class Notifications {
diff --git a/frontend/src/serviceWorker.js b/frontend/src/serviceWorker.js
index b04b771..c633a91 100644
--- a/frontend/src/serviceWorker.js
+++ b/frontend/src/serviceWorker.js
@@ -57,7 +57,7 @@ export function register(config) {
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
- .then(registration => {
+ .then((registration) => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
@@ -93,7 +93,7 @@ function registerValidSW(swUrl, config) {
};
};
})
- .catch(error => {
+ .catch((error) => {
console.error('Error during service worker registration:', error);
});
}
@@ -103,7 +103,7 @@ function checkValidServiceWorker(swUrl, config) {
fetch(swUrl, {
headers: { 'Service-Worker': 'script' },
})
- .then(response => {
+ .then((response) => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
@@ -111,7 +111,7 @@ function checkValidServiceWorker(swUrl, config) {
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
- navigator.serviceWorker.ready.then(registration => {
+ navigator.serviceWorker.ready.then((registration) => {
registration.unregister().then(() => {
window.location.reload();
});
@@ -131,10 +131,10 @@ function checkValidServiceWorker(swUrl, config) {
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready
- .then(registration => {
+ .then((registration) => {
registration.unregister();
})
- .catch(error => {
+ .catch((error) => {
console.error(error.message);
});
}
diff --git a/frontend/src/setupProxy.js b/frontend/src/setupProxy.js
index f2e1c39..fb60b75 100644
--- a/frontend/src/setupProxy.js
+++ b/frontend/src/setupProxy.js
@@ -15,10 +15,10 @@
* along with this program. If not, see .
*/
-const { createProxyMiddleware } = require('http-proxy-middleware');
+const {createProxyMiddleware} = require("http-proxy-middleware");
-module.exports = function(app) {
- app.use(createProxyMiddleware("/api", { target: "http://localhost:3333" }));
- app.use(createProxyMiddleware("/setup", { target: "http://localhost:3333" }));
- app.use(createProxyMiddleware("/ws", { target: "http://localhost:3333", ws: true }));
+module.exports = function (app) {
+ app.use(createProxyMiddleware("/api", {target: "http://localhost:3333"}));
+ app.use(createProxyMiddleware("/setup", {target: "http://localhost:3333"}));
+ app.use(createProxyMiddleware("/ws", {target: "http://localhost:3333", ws: true}));
};
diff --git a/frontend/src/setupTests.js b/frontend/src/setupTests.js
index 74b1a27..9b1d6d0 100644
--- a/frontend/src/setupTests.js
+++ b/frontend/src/setupTests.js
@@ -2,4 +2,4 @@
// 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';
+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 06414ac..0f0927e 100644
--- a/frontend/src/utils.js
+++ b/frontend/src/utils.js
@@ -18,15 +18,15 @@
const timeRegex = /^(0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/;
export function createCurlCommand(subCommand, method = null, json = null, data = null) {
- const full = window.location.protocol + '//' + window.location.hostname + (window.location.port ? ':' + window.location.port : '');
+ const full = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ":" + window.location.port : "");
let contentType = null;
let content = null;
if (json != null) {
- contentType = ' -H "Content-Type: application/json" \\\n';
+ contentType = " -H \"Content-Type: application/json\" \\\n";
content = ` -d '${JSON.stringify(json)}'`;
} else if (data != null) {
- contentType = ' -H "Content-Type: multipart/form-data" \\\n';
+ contentType = " -H \"Content-Type: multipart/form-data\" \\\n";
content = " " + Object.entries(data).map(([key, value]) => `-F "${key}=${value}"`).join(" \\\n ");
}
@@ -66,13 +66,13 @@ export function timeToTimestamp(time) {
let d = new Date();
let matches = time.match(timeRegex);
- if (matches[1] !== undefined) {
+ if (matches[1]) {
d.setHours(matches[1]);
}
- if (matches[2] !== undefined) {
+ if (matches[2]) {
d.setMinutes(matches[2]);
}
- if (matches[3] !== undefined) {
+ if (matches[3]) {
d.setSeconds(matches[3]);
}
@@ -84,7 +84,7 @@ export function timestampToTime(timestamp) {
let hours = d.getHours();
let minutes = "0" + d.getMinutes();
let seconds = "0" + d.getSeconds();
- return hours + ':' + minutes.substr(-2) + ':' + seconds.substr(-2);
+ return hours + ":" + minutes.substr(-2) + ":" + seconds.substr(-2);
}
export function timestampToDateTime(timestamp) {
@@ -100,7 +100,7 @@ export function dateTimeToTime(dateTime) {
let hours = dateTime.getHours();
let minutes = "0" + dateTime.getMinutes();
let seconds = "0" + dateTime.getSeconds();
- return hours + ':' + minutes.substr(-2) + ':' + seconds.substr(-2);
+ return hours + ":" + minutes.substr(-2) + ":" + seconds.substr(-2);
}
export function durationBetween(from, to) {
@@ -131,7 +131,7 @@ export function randomClassName() {
export function getHeaderValue(request, key) {
if (request && request.headers) {
- return request.headers[Object.keys(request.headers).find(k => k.toLowerCase() === key.toLowerCase())];
+ return request.headers[Object.keys(request.headers).find((k) => k.toLowerCase() === key.toLowerCase())];
}
return undefined;
}
@@ -154,7 +154,7 @@ export function updateParams(urlParams, payload) {
params.delete(key);
} else if (Array.isArray(value)) {
params.delete(key);
- value.forEach(v => params.append(key, v));
+ value.forEach((v) => params.append(key, v));
} else {
params.set(key, value);
}
--
cgit v1.2.3-70-g09d2
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
---
application_context.go | 12 +-
caronte.go | 2 +
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 ++++---
frontend/src/serviceWorker.js | 208 ++++++++++-----------
parsers/http_request_parser.go | 12 +-
pcap_importer.go | 39 ++--
19 files changed, 458 insertions(+), 353 deletions(-)
create mode 100644 frontend/src/components/objects/CopyLinkPopover.js
(limited to 'frontend/src/components/objects')
diff --git a/application_context.go b/application_context.go
index 8abb6f4..0410b88 100644
--- a/application_context.go
+++ b/application_context.go
@@ -39,6 +39,7 @@ type ApplicationContext struct {
ConnectionStreamsController ConnectionStreamsController
SearchController *SearchController
StatisticsController StatisticsController
+ NotificationController *NotificationController
IsConfigured bool
Version string
}
@@ -70,13 +71,12 @@ func CreateApplicationContext(storage Storage, version string) (*ApplicationCont
Version: version,
}
- applicationContext.configure()
return applicationContext, nil
}
func (sm *ApplicationContext) SetConfig(config Config) {
sm.Config = config
- sm.configure()
+ sm.Configure()
var upsertResults interface{}
if _, err := sm.Storage.Update(Settings).Upsert(&upsertResults).
Filter(OrderedDocument{{"_id", "config"}}).One(UnorderedDocument{"config": config}); err != nil {
@@ -93,7 +93,11 @@ func (sm *ApplicationContext) SetAccounts(accounts gin.Accounts) {
}
}
-func (sm *ApplicationContext) configure() {
+func (sm *ApplicationContext) SetNotificationController(notificationController *NotificationController) {
+ sm.NotificationController = notificationController
+}
+
+func (sm *ApplicationContext) Configure() {
if sm.IsConfigured {
return
}
@@ -110,7 +114,7 @@ func (sm *ApplicationContext) configure() {
log.WithError(err).Panic("failed to create a RulesManager")
}
sm.RulesManager = rulesManager
- sm.PcapImporter = NewPcapImporter(sm.Storage, *serverNet, sm.RulesManager)
+ sm.PcapImporter = NewPcapImporter(sm.Storage, *serverNet, sm.RulesManager, sm.NotificationController)
sm.ServicesController = NewServicesController(sm.Storage)
sm.SearchController = NewSearchController(sm.Storage)
sm.ConnectionsController = NewConnectionsController(sm.Storage, sm.SearchController, sm.ServicesController)
diff --git a/caronte.go b/caronte.go
index 2d24af6..95f00ef 100644
--- a/caronte.go
+++ b/caronte.go
@@ -51,10 +51,12 @@ func main() {
notificationController := NewNotificationController(applicationContext)
go notificationController.Run()
+ applicationContext.SetNotificationController(notificationController)
resourcesController := NewResourcesController(notificationController)
go resourcesController.Run()
+ applicationContext.Configure()
applicationRouter := CreateApplicationRouter(applicationContext, notificationController, resourcesController)
if applicationRouter.Run(fmt.Sprintf("%s:%v", *bindAddress, *bindPort)) != nil {
log.WithError(err).WithFields(logFields).Fatal("failed to create the server")
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 {
{}
-
+
diff --git a/frontend/src/serviceWorker.js b/frontend/src/serviceWorker.js
index c633a91..a1f0ba8 100644
--- a/frontend/src/serviceWorker.js
+++ b/frontend/src/serviceWorker.js
@@ -11,131 +11,131 @@
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
- window.location.hostname === 'localhost' ||
+ window.location.hostname === "localhost" ||
// [::1] is the IPv6 localhost address.
- window.location.hostname === '[::1]' ||
+ 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}$/
+ /^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;
- }
+ 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`;
+ 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);
+ 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'
- );
+ // 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);
+ }
});
- } 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.'
- );
+ 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.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);
- });
+ // 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);
- }
+ // Check if the service worker can be found. If it can't reload the page.
+ fetch(swUrl, {
+ headers: {"Service-Worker": "script"},
})
- .catch(() => {
- console.log(
- 'No internet connection found. App is running in offline mode.'
- );
- });
+ .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);
- });
- }
+ if ("serviceWorker" in navigator) {
+ navigator.serviceWorker.ready
+ .then((registration) => {
+ registration.unregister();
+ })
+ .catch((error) => {
+ console.error(error.message);
+ });
+ }
}
diff --git a/parsers/http_request_parser.go b/parsers/http_request_parser.go
index bc98f8f..56093c9 100644
--- a/parsers/http_request_parser.go
+++ b/parsers/http_request_parser.go
@@ -82,7 +82,7 @@ func (p HttpRequestParser) TryParse(content []byte) Metadata {
Trailer: JoinArrayMap(request.Trailer),
Reproducers: HttpRequestMetadataReproducers{
CurlCommand: curlCommand(content),
- RequestsCode: requestsCode(request),
+ RequestsCode: requestsCode(request, body),
FetchRequest: fetchRequest(request, body),
},
}
@@ -99,19 +99,15 @@ func curlCommand(content []byte) string {
}
}
-func requestsCode(request *http.Request) string {
+func requestsCode(request *http.Request, body string) string {
var b strings.Builder
- var params string
- if request.Form != nil {
- params = toJson(JoinArrayMap(request.PostForm))
- }
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() + "\"")
- if params != "" {
- b.WriteString(", data = " + params)
+ if body != "" {
+ b.WriteString(", data = \"" + strings.Replace(body, "\"", "\\\"", -1) + "\"")
}
if headers != "" {
b.WriteString(", headers = " + headers)
diff --git a/pcap_importer.go b/pcap_importer.go
index 41ed082..1c80b3f 100644
--- a/pcap_importer.go
+++ b/pcap_importer.go
@@ -29,6 +29,7 @@ import (
"os"
"path"
"path/filepath"
+ "sort"
"sync"
"time"
)
@@ -39,13 +40,14 @@ const initialAssemblerPoolSize = 16
const importUpdateProgressInterval = 100 * time.Millisecond
type PcapImporter struct {
- storage Storage
- streamPool *tcpassembly.StreamPool
- assemblers []*tcpassembly.Assembler
- sessions map[string]ImportingSession
- mAssemblers sync.Mutex
- mSessions sync.Mutex
- serverNet net.IPNet
+ storage Storage
+ streamPool *tcpassembly.StreamPool
+ assemblers []*tcpassembly.Assembler
+ sessions map[string]ImportingSession
+ mAssemblers sync.Mutex
+ mSessions sync.Mutex
+ serverNet net.IPNet
+ notificationController *NotificationController
}
type ImportingSession struct {
@@ -63,7 +65,8 @@ type ImportingSession struct {
type flowCount [2]int
-func NewPcapImporter(storage Storage, serverNet net.IPNet, rulesManager RulesManager) *PcapImporter {
+func NewPcapImporter(storage Storage, serverNet net.IPNet, rulesManager RulesManager,
+ notificationController *NotificationController) *PcapImporter {
streamPool := tcpassembly.NewStreamPool(NewBiDirectionalStreamFactory(storage, serverNet, rulesManager))
var result []ImportingSession
@@ -76,13 +79,14 @@ func NewPcapImporter(storage Storage, serverNet net.IPNet, rulesManager RulesMan
}
return &PcapImporter{
- storage: storage,
- streamPool: streamPool,
- assemblers: make([]*tcpassembly.Assembler, 0, initialAssemblerPoolSize),
- sessions: sessions,
- mAssemblers: sync.Mutex{},
- mSessions: sync.Mutex{},
- serverNet: serverNet,
+ storage: storage,
+ streamPool: streamPool,
+ assemblers: make([]*tcpassembly.Assembler, 0, initialAssemblerPoolSize),
+ sessions: sessions,
+ mAssemblers: sync.Mutex{},
+ mSessions: sync.Mutex{},
+ serverNet: serverNet,
+ notificationController: notificationController,
}
}
@@ -136,6 +140,9 @@ func (pi *PcapImporter) GetSessions() []ImportingSession {
for _, session := range pi.sessions {
sessions = append(sessions, session)
}
+ sort.Slice(sessions, func(i, j int) bool {
+ return sessions[i].StartedAt.Before(sessions[j].StartedAt)
+ })
pi.mSessions.Unlock()
return sessions
}
@@ -202,6 +209,8 @@ func (pi *PcapImporter) parsePcap(session ImportingSession, fileName string, flu
handle.Close()
pi.releaseAssembler(assembler)
pi.progressUpdate(session, fileName, true, "")
+ pi.notificationController.Notify("pcap.completed", session)
+
return
}
--
cgit v1.2.3-70-g09d2
From d429a344ef34bb9289616dc7ca11c161fabce5d7 Mon Sep 17 00:00:00 2001
From: Emiliano Ciavatta
Date: Fri, 16 Oct 2020 15:26:55 +0200
Subject: Refactor js arrow functions
---
frontend/src/components/App.js | 2 +-
frontend/src/components/Notifications.js | 2 +-
frontend/src/components/Timeline.js | 2 +-
frontend/src/components/filters/AdvancedFilters.js | 4 ++--
.../src/components/filters/ExitSearchFilter.js | 2 +-
.../components/filters/RulesConnectionsFilter.js | 2 +-
frontend/src/components/objects/Connection.js | 4 ++--
.../components/objects/ConnectionMatchedRules.js | 4 ++--
frontend/src/components/panels/ConnectionsPane.js | 10 +++++-----
frontend/src/components/panels/PcapsPane.js | 6 +++---
frontend/src/components/panels/RulesPane.js | 16 ++++++++--------
frontend/src/components/panels/SearchPane.js | 22 +++++++++++-----------
frontend/src/components/panels/ServicesPane.js | 10 +++++-----
frontend/src/components/panels/StreamsPane.js | 6 +++---
frontend/src/dispatcher.js | 8 ++++----
frontend/src/log.js | 2 +-
parsers/http_request_parser.go | 3 +--
17 files changed, 52 insertions(+), 53 deletions(-)
(limited to 'frontend/src/components/objects')
diff --git a/frontend/src/components/App.js b/frontend/src/components/App.js
index 888ff86..e12a99d 100644
--- a/frontend/src/components/App.js
+++ b/frontend/src/components/App.js
@@ -27,7 +27,7 @@ class App extends Component {
state = {};
componentDidMount() {
- dispatcher.register("notifications", payload => {
+ dispatcher.register("notifications", (payload) => {
if (payload.event === "connected") {
this.setState({
connected: true,
diff --git a/frontend/src/components/Notifications.js b/frontend/src/components/Notifications.js
index a81eba1..0b47b43 100644
--- a/frontend/src/components/Notifications.js
+++ b/frontend/src/components/Notifications.js
@@ -107,7 +107,7 @@ class Notifications extends Component {
{
- this.state.closedNotifications.concat(this.state.notifications).map(n => {
+ this.state.closedNotifications.concat(this.state.notifications).map((n) => {
const notificationClassnames = {
"notification": true,
"notification-closed": n.closed,
diff --git a/frontend/src/components/Timeline.js b/frontend/src/components/Timeline.js
index 94fa4d0..9ecbd80 100644
--- a/frontend/src/components/Timeline.js
+++ b/frontend/src/components/Timeline.js
@@ -127,7 +127,7 @@ class Timeline extends Component {
const series = new TimeSeries({
name: "statistics",
columns: ["time"].concat(columns),
- points: zeroFilledMetrics.map((m) => [m["range_start"]].concat(columns.map(c =>
+ points: zeroFilledMetrics.map((m) => [m["range_start"]].concat(columns.map((c) =>
((metric in m) && (m[metric] != null)) ? (m[metric][c] || 0) : 0
)))
});
diff --git a/frontend/src/components/filters/AdvancedFilters.js b/frontend/src/components/filters/AdvancedFilters.js
index 15667a5..8598185 100644
--- a/frontend/src/components/filters/AdvancedFilters.js
+++ b/frontend/src/components/filters/AdvancedFilters.js
@@ -28,10 +28,10 @@ class AdvancedFilters extends Component {
componentDidMount() {
this.urlParams = new URLSearchParams(this.props.location.search);
- this.connectionsFiltersCallback = payload => {
+ this.connectionsFiltersCallback = (payload) => {
this.urlParams = updateParams(this.urlParams, payload);
const active = ["client_address", "client_port", "min_duration", "max_duration", "min_bytes", "max_bytes"]
- .some(f => this.urlParams.has(f));
+ .some((f) => this.urlParams.has(f));
if (this.state.active !== active) {
this.setState({active});
}
diff --git a/frontend/src/components/filters/ExitSearchFilter.js b/frontend/src/components/filters/ExitSearchFilter.js
index 72cfb0c..0aacfd6 100644
--- a/frontend/src/components/filters/ExitSearchFilter.js
+++ b/frontend/src/components/filters/ExitSearchFilter.js
@@ -28,7 +28,7 @@ class ExitSearchFilter extends Component {
let params = new URLSearchParams(this.props.location.search);
this.setState({performedSearch: params.get("performed_search")});
- this.connectionsFiltersCallback = payload => {
+ this.connectionsFiltersCallback = (payload) => {
if (this.state.performedSearch !== payload["performed_search"]) {
this.setState({performedSearch: payload["performed_search"]});
}
diff --git a/frontend/src/components/filters/RulesConnectionsFilter.js b/frontend/src/components/filters/RulesConnectionsFilter.js
index 37d36b7..210ee36 100644
--- a/frontend/src/components/filters/RulesConnectionsFilter.js
+++ b/frontend/src/components/filters/RulesConnectionsFilter.js
@@ -37,7 +37,7 @@ class RulesConnectionsFilter extends Component {
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));
+ activeRules = rules.filter((rule) => activeRules.some((id) => rule.id === id));
this.setState({rules, activeRules});
});
diff --git a/frontend/src/components/objects/Connection.js b/frontend/src/components/objects/Connection.js
index e5896e9..b70b7f7 100644
--- a/frontend/src/components/objects/Connection.js
+++ b/frontend/src/components/objects/Connection.js
@@ -37,7 +37,7 @@ class Connection extends Component {
if (name === "hide") {
const enabled = !this.props.data.hidden;
backend.post(`/api/connections/${this.props.data.id}/${enabled ? "hide" : "show"}`)
- .then(_ => {
+ .then((_) => {
this.props.onEnabled(!enabled);
this.setState({update: true});
});
@@ -45,7 +45,7 @@ class Connection extends Component {
if (name === "mark") {
const marked = this.props.data.marked;
backend.post(`/api/connections/${this.props.data.id}/${marked ? "unmark" : "mark"}`)
- .then(_ => {
+ .then((_) => {
this.props.onMarked(!marked);
this.setState({update: true});
});
diff --git a/frontend/src/components/objects/ConnectionMatchedRules.js b/frontend/src/components/objects/ConnectionMatchedRules.js
index cfd1254..a69cad8 100644
--- a/frontend/src/components/objects/ConnectionMatchedRules.js
+++ b/frontend/src/components/objects/ConnectionMatchedRules.js
@@ -33,8 +33,8 @@ class ConnectionMatchedRules extends Component {
};
render() {
- const matchedRules = this.props.matchedRules.map(mr => {
- const rule = this.props.rules.find(r => r.id === mr);
+ const matchedRules = this.props.matchedRules.map((mr) => {
+ const rule = this.props.rules.find((r) => r.id === mr);
return this.onMatchedRulesSelected(rule.id)} name={rule.name}
color={rule.color} small/>;
});
diff --git a/frontend/src/components/panels/ConnectionsPane.js b/frontend/src/components/panels/ConnectionsPane.js
index 9418fad..457c249 100644
--- a/frontend/src/components/panels/ConnectionsPane.js
+++ b/frontend/src/components/panels/ConnectionsPane.js
@@ -61,8 +61,8 @@ class ConnectionsPane extends Component {
const id = match[1];
additionalParams.from = id;
backend.get(`/api/connections/${id}`)
- .then(res => this.connectionSelected(res.json))
- .catch(error => log.error("Error loading initial connection", error));
+ .then((res) => this.connectionSelected(res.json))
+ .catch((error) => log.error("Error loading initial connection", error));
}
this.loadConnections(additionalParams, urlParams, true).then(() => log.debug("Connections loaded"));
@@ -283,11 +283,11 @@ class ConnectionsPane extends Component {
{
- this.state.connections.flatMap(c => {
+ this.state.connections.flatMap((c) => {
return [ this.connectionSelected(c)}
selected={this.state.selected === c.id}
- onMarked={marked => c.marked = marked}
- onEnabled={enabled => c.hidden = !enabled}
+ onMarked={(marked) => c.marked = marked}
+ onEnabled={(enabled) => c.hidden = !enabled}
services={this.state.services}/>,
c.matched_rules.length > 0 &&
{
this.setState({
processStatusCode: res.status,
diff --git a/frontend/src/components/panels/RulesPane.js b/frontend/src/components/panels/RulesPane.js
index 0bbd407..cdfe185 100644
--- a/frontend/src/components/panels/RulesPane.js
+++ b/frontend/src/components/panels/RulesPane.js
@@ -103,17 +103,17 @@ class RulesPane extends Component {
};
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)}));
+ backend.get("/api/rules").then((res) => this.setState({rules: res.json, rulesStatusCode: res.status}))
+ .catch((res) => this.setState({rulesStatusCode: res.status, rulesResponse: JSON.stringify(res.json)}));
};
addRule = () => {
if (this.validateRule(this.state.newRule)) {
- backend.post("/api/rules", this.state.newRule).then(res => {
+ backend.post("/api/rules", this.state.newRule).then((res) => {
this.reset();
this.setState({ruleStatusCode: res.status});
this.loadRules();
- }).catch(res => {
+ }).catch((res) => {
this.setState({ruleStatusCode: res.status, ruleResponse: JSON.stringify(res.json)});
});
}
@@ -122,11 +122,11 @@ class RulesPane extends Component {
updateRule = () => {
const rule = this.state.selectedRule;
if (this.validateRule(rule)) {
- backend.put(`/api/rules/${rule.id}`, rule).then(res => {
+ backend.put(`/api/rules/${rule.id}`, rule).then((res) => {
this.reset();
this.setState({ruleStatusCode: res.status});
this.loadRules();
- }).catch(res => {
+ }).catch((res) => {
this.setState({ruleStatusCode: res.status, ruleResponse: JSON.stringify(res.json)});
});
}
@@ -247,7 +247,7 @@ class RulesPane extends Component {
const rule = this.currentRule();
const pattern = this.state.selectedPattern || this.state.newPattern;
- let rules = this.state.rules.map(r =>
+ let rules = this.state.rules.map((r) =>
{
this.reset();
this.setState({selectedRule: _.cloneDeep(r)});
@@ -262,7 +262,7 @@ class RulesPane extends Component {
let patterns = (this.state.selectedPattern == null && !isUpdate ?
rule.patterns.concat(this.state.newPattern) :
rule.patterns
- ).map(p => p === pattern ?
+ ).map((p) => p === pattern ?
- {
+ {
return {name: t};
})}
name="terms" min={3} inline allowNew={true}
readonly={regexOptionsModified || options["text_search"]["exact_phrase"]}
- onChange={(tags) => this.updateParam(s => s["text_search"].terms = tags.map(t => t.name))}/>
+ onChange={(tags) => this.updateParam((s) => s["text_search"].terms = tags.map(t => t.name))}/>
{
return {name: t};
})}
name="excluded_terms" min={3} inline allowNew={true}
readonly={regexOptionsModified || options["text_search"]["exact_phrase"]}
- onChange={(tags) => this.updateParam(s => s["text_search"]["excluded_terms"] = tags.map(t => t.name))}/>
+ onChange={(tags) => this.updateParam((s) => s["text_search"]["excluded_terms"] = tags.map(t => t.name))}/>
or
this.updateParam(s => s["text_search"]["exact_phrase"] = v)}
+ onChange={(v) => this.updateParam((s) => s["text_search"]["exact_phrase"] = v)}
readonly={regexOptionsModified || (Array.isArray(options["text_search"].terms) && options["text_search"].terms.length > 0)}/>
this.updateParam(s => s["text_search"]["case_sensitive"] = v)}/>
+ onChange={(v) => this.updateParam((s) => s["text_search"]["case_sensitive"] = v)}/>
@@ -264,28 +264,28 @@ class SearchPane extends Component {
this.updateParam(s => s["regex_search"].pattern = v)}/>
+ onChange={(v) => this.updateParam((s) => s["regex_search"].pattern = v)}/>
or
this.updateParam(s => s["regex_search"]["not_pattern"] = v)}/>
+ onChange={(v) => this.updateParam((s) => s["regex_search"]["not_pattern"] = v)}/>
this.updateParam(s => s["regex_search"]["case_insensitive"] = v)}/>
+ onChange={(v) => this.updateParam((s) => s["regex_search"]["case_insensitive"] = v)}/>
this.updateParam(s => s["regex_search"]["multi_line"] = v)}/>
+ onChange={(v) => this.updateParam((s) => s["regex_search"]["multi_line"] = v)}/>
this.updateParam(s => s["regex_search"]["ignore_whitespaces"] = v)}/>
+ onChange={(v) => this.updateParam((s) => s["regex_search"]["ignore_whitespaces"] = v)}/>
this.updateParam(s => s["regex_search"]["dot_character"] = v)}/>
+ onChange={(v) => this.updateParam((s) => s["regex_search"]["dot_character"] = v)}/>
diff --git a/frontend/src/components/panels/ServicesPane.js b/frontend/src/components/panels/ServicesPane.js
index 48d9e29..5986804 100644
--- a/frontend/src/components/panels/ServicesPane.js
+++ b/frontend/src/components/panels/ServicesPane.js
@@ -68,18 +68,18 @@ class ServicesPane extends Component {
loadServices = () => {
backend.get("/api/services")
- .then(res => this.setState({services: Object.values(res.json), servicesStatusCode: res.status}))
- .catch(res => this.setState({servicesStatusCode: res.status, servicesResponse: JSON.stringify(res.json)}));
+ .then((res) => this.setState({services: Object.values(res.json), servicesStatusCode: res.status}))
+ .catch((res) => this.setState({servicesStatusCode: res.status, servicesResponse: JSON.stringify(res.json)}));
};
updateService = () => {
const service = this.state.currentService;
if (this.validateService(service)) {
- backend.put("/api/services", service).then(res => {
+ backend.put("/api/services", service).then((res) => {
this.reset();
this.setState({serviceStatusCode: res.status});
this.loadServices();
- }).catch(res => {
+ }).catch((res) => {
this.setState({serviceStatusCode: res.status, serviceResponse: JSON.stringify(res.json)});
});
}
@@ -126,7 +126,7 @@ class ServicesPane extends Component {
const isUpdate = this.state.isUpdate;
const service = this.state.currentService;
- let services = this.state.services.map(s =>
+ let services = this.state.services.map((s) =>
{
this.reset();
this.setState({isUpdate: true, currentService: _.cloneDeep(s)});
diff --git a/frontend/src/components/panels/StreamsPane.js b/frontend/src/components/panels/StreamsPane.js
index 1819fec..9470d7d 100644
--- a/frontend/src/components/panels/StreamsPane.js
+++ b/frontend/src/components/panels/StreamsPane.js
@@ -67,7 +67,7 @@ class StreamsPane extends Component {
loadStream = (connectionId) => {
this.setState({messages: [], currentId: connectionId});
backend.get(`/api/streams/${connectionId}?format=${this.state.format}`)
- .then(res => this.setState({messages: res.json}));
+ .then((res) => this.setState({messages: res.json}));
};
setFormat = (format) => {
@@ -166,8 +166,8 @@ class StreamsPane extends Component {
downloadStreamRaw = (value) => {
if (this.state.currentId) {
backend.download(`/api/streams/${this.props.connection.id}/download?format=${this.state.format}&type=${value}`)
- .then(res => downloadBlob(res.blob, `${this.state.currentId}-${value}-${this.state.format}.txt`))
- .catch(_ => log.error("Failed to download stream messages"));
+ .then((res) => downloadBlob(res.blob, `${this.state.currentId}-${value}-${this.state.format}.txt`))
+ .catch((_) => log.error("Failed to download stream messages"));
}
};
diff --git a/frontend/src/dispatcher.js b/frontend/src/dispatcher.js
index ef5dbde..32f3f33 100644
--- a/frontend/src/dispatcher.js
+++ b/frontend/src/dispatcher.js
@@ -24,7 +24,7 @@ class Dispatcher {
}
dispatch = (topic, payload) => {
- this.listeners.filter(l => l.topic === topic).forEach(l => l.callback(payload));
+ this.listeners.filter((l) => l.topic === topic).forEach((l) => l.callback(payload));
};
register = (topic, callback) => {
@@ -34,20 +34,20 @@ class Dispatcher {
if (typeof topic === "string") {
this.listeners.push({topic, callback});
} else if (typeof topic === "object" && Array.isArray(topic)) {
- topic.forEach(e => {
+ topic.forEach((e) => {
if (typeof e !== "string") {
throw new Error("all topics must be strings");
}
});
- topic.forEach(e => this.listeners.push({e, callback}));
+ topic.forEach((e) => this.listeners.push({e, callback}));
} else {
throw new Error("topic must be a string or an array of strings");
}
};
unregister = (callback) => {
- _.remove(this.listeners, l => l.callback === callback);
+ _.remove(this.listeners, (l) => l.callback === callback);
};
}
diff --git a/frontend/src/log.js b/frontend/src/log.js
index 0afac47..9a75fbc 100644
--- a/frontend/src/log.js
+++ b/frontend/src/log.js
@@ -23,7 +23,7 @@ const log = {
};
function isDevelopment() {
- return !process.env.NODE_ENV || process.env.NODE_ENV === 'development';
+ return !process.env.NODE_ENV || process.env.NODE_ENV === "development";
}
export default log;
diff --git a/parsers/http_request_parser.go b/parsers/http_request_parser.go
index d7ed81c..98ba8e3 100644
--- a/parsers/http_request_parser.go
+++ b/parsers/http_request_parser.go
@@ -148,9 +148,8 @@ func fetchRequest(request *http.Request, body string) string {
if jsonData := toJSON(data); jsonData != "" {
return "fetch(\"" + request.URL.String() + "\", " + jsonData + ");"
- } else {
- return "invalid-request"
}
+ return "invalid-request"
}
func toJSON(obj interface{}) string {
--
cgit v1.2.3-70-g09d2
From 79b8b2fa3e8563c986da8baa3a761f2d4f0c6f47 Mon Sep 17 00:00:00 2001
From: Emiliano Ciavatta
Date: Fri, 16 Oct 2020 19:05:44 +0200
Subject: Minor improvements
---
application_router.go | 2 +-
frontend/src/components/App.js | 29 ++++++++-----
frontend/src/components/Header.js | 12 +++---
frontend/src/components/fields/InputField.js | 2 +-
frontend/src/components/objects/CopyLinkPopover.js | 4 +-
frontend/src/components/pages/ConfigurationPage.js | 12 ++++--
.../src/components/pages/ConfigurationPage.scss | 30 +++++++++----
frontend/src/components/pages/MainPage.js | 50 +++++++++++-----------
frontend/src/components/panels/ConnectionsPane.js | 4 +-
frontend/src/components/panels/SearchPane.js | 6 +--
rules_manager.go | 5 +++
11 files changed, 93 insertions(+), 63 deletions(-)
(limited to 'frontend/src/components/objects')
diff --git a/application_router.go b/application_router.go
index 656b63e..4048dc5 100644
--- a/application_router.go
+++ b/application_router.go
@@ -39,7 +39,7 @@ func CreateApplicationRouter(applicationContext *ApplicationContext,
router.Use(static.Serve("/", static.LocalFile("./frontend/build", true)))
- for _, path := range []string{"/connections/:id", "/pcaps", "/rules", "/services", "/config"} {
+ for _, path := range []string{"/connections/:id", "/pcaps", "/rules", "/services", "/config", "/searches"} {
router.GET(path, func(c *gin.Context) {
c.File("./frontend/build/index.html")
})
diff --git a/frontend/src/components/App.js b/frontend/src/components/App.js
index e12a99d..96083cd 100644
--- a/frontend/src/components/App.js
+++ b/frontend/src/components/App.js
@@ -16,6 +16,7 @@
*/
import React, {Component} from "react";
+import {BrowserRouter as Router} from "react-router-dom";
import dispatcher from "../dispatcher";
import Notifications from "./Notifications";
import ConfigurationPage from "./pages/ConfigurationPage";
@@ -27,15 +28,7 @@ class App extends Component {
state = {};
componentDidMount() {
- dispatcher.register("notifications", (payload) => {
- if (payload.event === "connected") {
- this.setState({
- connected: true,
- configured: payload.message["is_configured"],
- version: payload.message["version"]
- });
- }
- });
+ dispatcher.register("notifications", this.handleNotifications);
setInterval(() => {
if (document.title.endsWith("❚")) {
@@ -46,16 +39,30 @@ class App extends Component {
}, 500);
}
+ componentWillUnmount() {
+ dispatcher.unregister(this.handleNotifications);
+ }
+
+ handleNotifications = (payload) => {
+ if (payload.event === "connected") {
+ this.setState({
+ connected: true,
+ configured: payload.message["is_configured"],
+ version: payload.message["version"]
+ });
+ }
+ };
+
render() {
return (
- <>
+
{this.state.connected ?
(this.state.configured ? :
this.setState({configured: true})}/>) :
}
- >
+
);
}
}
diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js
index ab16dd1..c46d768 100644
--- a/frontend/src/components/Header.js
+++ b/frontend/src/components/Header.js
@@ -27,6 +27,8 @@ import RulesConnectionsFilter from "./filters/RulesConnectionsFilter";
import StringConnectionsFilter from "./filters/StringConnectionsFilter";
import "./Header.scss";
+const classNames = require("classnames");
+
class Header extends Component {
componentDidMount() {
@@ -46,7 +48,7 @@ class Header extends Component {
return (
-
+
{
@@ -56,7 +58,7 @@ class Header extends Component {
-
+ {this.props.configured &&
+
}
-
+ {this.props.configured &&
@@ -89,7 +91,7 @@ class Header extends Component {
-
+
}
);
diff --git a/frontend/src/components/fields/InputField.js b/frontend/src/components/fields/InputField.js
index 823989d..e2ea020 100644
--- a/frontend/src/components/fields/InputField.js
+++ b/frontend/src/components/fields/InputField.js
@@ -75,7 +75,7 @@ class InputField extends Component {
aria-describedby={this.id} onChange={handler} {...inputProps}
readOnly={this.props.readonly}/>
- {type !== "file" && value !== "" &&
+ {type !== "file" && value !== "" && !this.props.readonly &&
handler(null)}>del
diff --git a/frontend/src/components/objects/CopyLinkPopover.js b/frontend/src/components/objects/CopyLinkPopover.js
index fa9266f..b951603 100644
--- a/frontend/src/components/objects/CopyLinkPopover.js
+++ b/frontend/src/components/objects/CopyLinkPopover.js
@@ -37,10 +37,10 @@ class CopyLinkPopover extends Component {
};
render() {
- const copyPopoverContent =
+ const copyPopoverContent =
{this.state.copiedMessage ? Copied! :
Click to copy }
-
+
;
return (
diff --git a/frontend/src/components/pages/ConfigurationPage.js b/frontend/src/components/pages/ConfigurationPage.js
index 4f0ce21..8f9b68b 100644
--- a/frontend/src/components/pages/ConfigurationPage.js
+++ b/frontend/src/components/pages/ConfigurationPage.js
@@ -25,8 +25,10 @@ import ButtonField from "../fields/ButtonField";
import CheckField from "../fields/CheckField";
import InputField from "../fields/InputField";
import TextField from "../fields/TextField";
+import Header from "../Header";
import LinkPopover from "../objects/LinkPopover";
import "../panels/common.scss";
+import "./common.scss";
import "./ConfigurationPage.scss";
class ConfigurationPage extends Component {
@@ -113,9 +115,13 @@ class ConfigurationPage extends Component {
);
return (
-
-
-
+
+
+
+
+
+
+
POST /setup
diff --git a/frontend/src/components/pages/ConfigurationPage.scss b/frontend/src/components/pages/ConfigurationPage.scss
index 4509865..4254547 100644
--- a/frontend/src/components/pages/ConfigurationPage.scss
+++ b/frontend/src/components/pages/ConfigurationPage.scss
@@ -1,18 +1,30 @@
@import "../../colors";
.configuration-page {
- display: flex;
- align-items: center;
- justify-content: center;
- height: 100%;
background-color: $color-primary-0;
- .pane {
- flex-basis: 900px;
- margin-bottom: 200px;
+ .header-title {
+ margin: 50px auto;
}
- .pane-container {
- padding-bottom: 1px;
+ .configuration-pane {
+ display: flex;
+ justify-content: center;
+ height: 100%;
+ padding-top: 100px;
+
+ .section-content {
+ background-color: $color-primary-3;
+ margin-top: 15px;
+ }
+
+ .section-table table {
+ background-color: red !important;
+ }
+
+ .section-footer {
+ background-color: $color-primary-3;
+ }
}
}
+
diff --git a/frontend/src/components/pages/MainPage.js b/frontend/src/components/pages/MainPage.js
index a542e3f..c4dcd20 100644
--- a/frontend/src/components/pages/MainPage.js
+++ b/frontend/src/components/pages/MainPage.js
@@ -16,7 +16,7 @@
*/
import React, {Component} from "react";
-import {BrowserRouter as Router, Route, Switch} from "react-router-dom";
+import {Route, Switch} from "react-router-dom";
import Filters from "../dialogs/Filters";
import Header from "../Header";
import Connections from "../panels/ConnectionsPane";
@@ -42,34 +42,32 @@ class MainPage extends Component {
return (
-
-
- this.setState({filterWindowOpen: true})}/>
-
-
-
-
- this.setState({selectedConnection: c})}/>
-
-
-
- }/>
- }/>
- }/>
- }/>
- }/>
- }/>
-
-
+
+ this.setState({filterWindowOpen: true})} configured={true}/>
+
- {modal}
+
+
+ this.setState({selectedConnection: c})}/>
-
-
-
+
+
+ }/>
+ }/>
+ }/>
+ }/>
+ }/>
+ }/>
+
-
+
+ {modal}
+
+
+
+
+
);
}
diff --git a/frontend/src/components/panels/ConnectionsPane.js b/frontend/src/components/panels/ConnectionsPane.js
index 457c249..6418b3e 100644
--- a/frontend/src/components/panels/ConnectionsPane.js
+++ b/frontend/src/components/panels/ConnectionsPane.js
@@ -226,11 +226,11 @@ class ConnectionsPane extends Component {
}
loadRules = async () => {
- return backend.get("/api/rules").then(res => this.setState({rules: res.json}));
+ return backend.get("/api/rules").then((res) => this.setState({rules: res.json}));
};
loadServices = async () => {
- return backend.get("/api/services").then(res => this.setState({services: res.json}));
+ return backend.get("/api/services").then((res) => this.setState({services: res.json}));
};
render() {
diff --git a/frontend/src/components/panels/SearchPane.js b/frontend/src/components/panels/SearchPane.js
index 4c9f229..4ef5632 100644
--- a/frontend/src/components/panels/SearchPane.js
+++ b/frontend/src/components/panels/SearchPane.js
@@ -236,13 +236,13 @@ class SearchPane extends Component {
})}
name="terms" min={3} inline allowNew={true}
readonly={regexOptionsModified || options["text_search"]["exact_phrase"]}
- onChange={(tags) => this.updateParam((s) => s["text_search"].terms = tags.map(t => t.name))}/>
-
{
+ onChange={(tags) => this.updateParam((s) => s["text_search"].terms = tags.map((t) => t.name))}/>
+ {
return {name: t};
})}
name="excluded_terms" min={3} inline allowNew={true}
readonly={regexOptionsModified || options["text_search"]["exact_phrase"]}
- onChange={(tags) => this.updateParam((s) => s["text_search"]["excluded_terms"] = tags.map(t => t.name))}/>
+ onChange={(tags) => this.updateParam((s) => s["text_search"]["excluded_terms"] = tags.map((t) => t.name))}/>
or
diff --git a/rules_manager.go b/rules_manager.go
index a6d969f..5d6cded 100644
--- a/rules_manager.go
+++ b/rules_manager.go
@@ -24,6 +24,7 @@ import (
"github.com/flier/gohs/hyperscan"
"github.com/go-playground/validator/v10"
log "github.com/sirupsen/logrus"
+ "sort"
"sync"
"time"
)
@@ -213,6 +214,10 @@ func (rm *rulesManagerImpl) GetRules() []Rule {
rules = append(rules, rule)
}
+ sort.Slice(rules, func(i, j int) bool {
+ return rules[i].ID.Timestamp().Before(rules[j].ID.Timestamp())
+ })
+
return rules
}
--
cgit v1.2.3-70-g09d2