From 1412a34f64e234dbc7d4e6815b841699f4dd104a Mon Sep 17 00:00:00 2001
From: Emiliano Ciavatta
Date: Sun, 27 Sep 2020 17:48:38 +0200
Subject: Add other custom fields
---
frontend/src/backend.js | 27 +++-
frontend/src/components/ConnectionContent.js | 28 ++--
frontend/src/components/MessageAction.js | 13 +-
frontend/src/components/fields/ButtonField.js | 44 ++++++
frontend/src/components/fields/ButtonField.scss | 119 +++++++++++++++
frontend/src/components/fields/CheckField.js | 5 +-
frontend/src/components/fields/ChoiceField.js | 59 ++++++++
frontend/src/components/fields/ChoiceField.scss | 69 +++++++++
frontend/src/components/fields/InputField.js | 7 +-
frontend/src/components/fields/InputField.scss | 28 +---
frontend/src/components/fields/TextField.js | 5 +-
frontend/src/components/fields/TextField.scss | 25 ----
frontend/src/components/fields/common.scss | 54 +++++++
.../src/components/fields/extensions/ColorField.js | 63 ++++++++
.../components/fields/extensions/ColorField.scss | 39 +++++
.../components/fields/extensions/NumericField.js | 39 +++++
.../components/filters/RulesConnectionsFilter.js | 2 +-
frontend/src/components/panels/PcapPane.js | 110 +++++++++-----
frontend/src/components/panels/RulePane.js | 166 +++++++++++++++++++++
frontend/src/components/panels/RulePane.scss | 16 ++
frontend/src/index.scss | 116 +-------------
frontend/src/views/Connections.js | 24 +--
frontend/src/views/Header.js | 4 +-
frontend/src/views/MainPane.js | 27 ++--
24 files changed, 841 insertions(+), 248 deletions(-)
create mode 100644 frontend/src/components/fields/ButtonField.js
create mode 100644 frontend/src/components/fields/ButtonField.scss
create mode 100644 frontend/src/components/fields/ChoiceField.js
create mode 100644 frontend/src/components/fields/ChoiceField.scss
create mode 100644 frontend/src/components/fields/common.scss
create mode 100644 frontend/src/components/fields/extensions/ColorField.js
create mode 100644 frontend/src/components/fields/extensions/ColorField.scss
create mode 100644 frontend/src/components/fields/extensions/NumericField.js
create mode 100644 frontend/src/components/panels/RulePane.js
create mode 100644 frontend/src/components/panels/RulePane.scss
(limited to 'frontend')
diff --git a/frontend/src/backend.js b/frontend/src/backend.js
index 5eb0e40..a02f7a8 100644
--- a/frontend/src/backend.js
+++ b/frontend/src/backend.js
@@ -1,5 +1,5 @@
-async function json(method, url, data, headers) {
+async function json(method, url, data, headers, returnJson) {
const options = {
method: method,
mode: "cors",
@@ -15,7 +15,18 @@ async function json(method, url, data, headers) {
options.body = JSON.stringify(data);
}
const result = await fetch(url, options);
- return result.json();
+ if (returnJson) {
+ if (result.status >= 200 && result.status < 300) {
+ return result.json();
+ } else {
+ return Promise.reject({
+ response: result,
+ json: await result.json()
+ });
+ }
+ } else {
+ return result;
+ }
}
async function file(url, data, headers) {
@@ -44,6 +55,18 @@ const backend = {
delete: (url = "", data = null, headers = null) => {
return json("DELETE", url, data, headers);
},
+ getJson: (url = "", headers = null) => {
+ return json("GET", url, null, headers, true);
+ },
+ postJson: (url = "", data = null, headers = null) => {
+ return json("POST", url, data, headers, true);
+ },
+ putJson: (url = "", data = null, headers = null) => {
+ return json("PUT", url, data, headers, true);
+ },
+ deleteJson: (url = "", data = null, headers = null) => {
+ return json("DELETE", url, data, headers, true);
+ },
postFile: (url = "", data = null, headers = null) => {
return file(url, data, headers);
},
diff --git a/frontend/src/components/ConnectionContent.js b/frontend/src/components/ConnectionContent.js
index 0c00e8e..0069424 100644
--- a/frontend/src/components/ConnectionContent.js
+++ b/frontend/src/components/ConnectionContent.js
@@ -22,20 +22,30 @@ class ConnectionContent extends Component {
this.setFormat = this.setFormat.bind(this);
}
+ componentDidMount() {
+ if (this.props.connection != null) {
+ this.loadStream();
+ }
+ }
+
componentDidUpdate(prevProps, prevState, snapshot) {
- if (this.props.connection !== null && (
+ if (this.props.connection != null && (
this.props.connection !== prevProps.connection || this.state.format !== prevState.format)) {
- this.setState({loading: true});
- // TODO: limit workaround.
- backend.get(`/api/streams/${this.props.connection.id}?format=${this.state.format}&limit=999999`).then(res => {
- this.setState({
- connectionContent: res,
- loading: false
- });
- });
+ this.loadStream();
}
}
+ loadStream = () => {
+ this.setState({loading: true});
+ // TODO: limit workaround.
+ backend.getJson(`/api/streams/${this.props.connection.id}?format=${this.state.format}&limit=999999`).then(res => {
+ this.setState({
+ connectionContent: res,
+ loading: false
+ });
+ });
+ };
+
setFormat(format) {
if (this.validFormats.includes(format)) {
this.setState({format: format});
diff --git a/frontend/src/components/MessageAction.js b/frontend/src/components/MessageAction.js
index 2c85d84..2c6ebbc 100644
--- a/frontend/src/components/MessageAction.js
+++ b/frontend/src/components/MessageAction.js
@@ -1,6 +1,8 @@
import React, {Component} from 'react';
import './MessageAction.scss';
-import {Button, FormControl, InputGroup, Modal} from "react-bootstrap";
+import {Modal} from "react-bootstrap";
+import TextField from "./fields/TextField";
+import ButtonField from "./fields/ButtonField";
class MessageAction extends Component {
@@ -35,14 +37,11 @@ class MessageAction extends Component {
-
-
-
+
-
-
+
+
);
diff --git a/frontend/src/components/fields/ButtonField.js b/frontend/src/components/fields/ButtonField.js
new file mode 100644
index 0000000..b32aee8
--- /dev/null
+++ b/frontend/src/components/fields/ButtonField.js
@@ -0,0 +1,44 @@
+import React, {Component} from 'react';
+import './ButtonField.scss';
+import './common.scss';
+
+const classNames = require('classnames');
+
+class ButtonField extends Component {
+
+ constructor(props) {
+ super(props);
+ }
+
+ render() {
+ const handler = () => {
+ if (typeof this.props.onClick === "function") {
+ this.props.onClick();
+ }
+ };
+
+ let buttonClassnames = {
+ "button-bordered": this.props.bordered,
+ };
+ if (this.props.variant) {
+ buttonClassnames[`button-variant-${this.props.variant}`] = true;
+ }
+
+ let buttonStyle = {};
+ if (this.props.color) {
+ buttonStyle["backgroundColor"] = this.props.color;
+ }
+ if (this.props.border) {
+ buttonStyle["borderColor"] = this.props.border;
+ }
+
+ return (
+
+
+
+ );
+ }
+}
+
+export default ButtonField;
diff --git a/frontend/src/components/fields/ButtonField.scss b/frontend/src/components/fields/ButtonField.scss
new file mode 100644
index 0000000..933279e
--- /dev/null
+++ b/frontend/src/components/fields/ButtonField.scss
@@ -0,0 +1,119 @@
+@import '../../colors.scss';
+
+.button-field {
+ font-size: 0.9em;
+
+ .button-bordered {
+ border-bottom: 5px solid $color-primary-1;
+ }
+
+ &.field-small {
+ font-size: 0.8em;
+ }
+
+ .button-variant-red {
+ color: $color-red-light;
+ background-color: $color-red;
+
+ &.button-bordered {
+ border-bottom: 5px solid $color-red-dark;
+ }
+
+ &:hover,
+ &:active {
+ color: $color-red-light;
+ background-color: $color-red-dark;
+ }
+ }
+
+ .button-variant-pink {
+ color: $color-pink-light;
+ background-color: $color-pink;
+
+ &.button-bordered {
+ border-bottom: 5px solid $color-pink-dark;
+ }
+
+ &:hover,
+ &:active {
+ color: $color-pink-light;
+ background-color: $color-pink-dark;
+ }
+ }
+
+ .button-variant-purple {
+ color: $color-purple-light;
+ background-color: $color-purple;
+
+ &.button-bordered {
+ border-bottom: 5px solid $color-purple-dark;
+ }
+
+ &:hover,
+ &:active {
+ color: $color-purple-light;
+ background-color: $color-purple-dark;
+ }
+ }
+
+ .button-variant-deep-purple {
+ color: $color-deep-purple-light;
+ background-color: $color-deep-purple;
+
+ &.button-bordered {
+ border-bottom: 5px solid $color-deep-purple-dark;
+ }
+
+ &:hover,
+ &:active {
+ color: $color-deep-purple-light;
+ background-color: $color-deep-purple-dark;
+ }
+ }
+
+ .button-variant-indigo {
+ color: $color-indigo-light;
+ background-color: $color-indigo;
+
+ &.button-bordered {
+ border-bottom: 5px solid $color-indigo-dark;
+ }
+
+ &:hover,
+ &:active {
+ color: $color-indigo-light;
+ background-color: $color-indigo-dark;
+ }
+ }
+
+ .button-variant-blue {
+ color: $color-blue-light;
+ background-color: $color-blue;
+
+ &.button-bordered {
+ border-bottom: 5px solid $color-blue-dark;
+ }
+
+ &:hover,
+ &:active {
+ color: $color-blue-light;
+ background-color: $color-blue-dark;
+ }
+ }
+
+ .button-variant-green {
+ color: $color-green-light;
+ background-color: $color-green;
+
+ &.button-bordered {
+ border-bottom: 5px solid $color-green-dark;
+ }
+
+ &:hover,
+ &:active {
+ color: $color-green-light;
+ background-color: $color-green-dark;
+ }
+ }
+
+}
diff --git a/frontend/src/components/fields/CheckField.js b/frontend/src/components/fields/CheckField.js
index 5cceac4..33f4f83 100644
--- a/frontend/src/components/fields/CheckField.js
+++ b/frontend/src/components/fields/CheckField.js
@@ -1,5 +1,6 @@
import React, {Component} from 'react';
import './CheckField.scss';
+import './common.scss';
import {randomClassName} from "../../utils";
const classNames = require('classnames');
@@ -23,10 +24,10 @@ class CheckField extends Component {
};
return (
-
+
);
diff --git a/frontend/src/components/fields/ChoiceField.js b/frontend/src/components/fields/ChoiceField.js
new file mode 100644
index 0000000..d409b21
--- /dev/null
+++ b/frontend/src/components/fields/ChoiceField.js
@@ -0,0 +1,59 @@
+import React, {Component} from 'react';
+import './ChoiceField.scss';
+import './common.scss';
+import {randomClassName} from "../../utils";
+
+const classNames = require('classnames');
+
+class ChoiceField extends Component {
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ expanded: false
+ };
+
+ this.id = `field-${this.props.name || "noname"}-${randomClassName()}`;
+ }
+
+ render() {
+ const name = this.props.name || null;
+ const inline = this.props.inline;
+
+ const collapse = () => this.setState({expanded: false});
+ const expand = () => this.setState({expanded: true});
+
+ const handler = (key) => {
+ collapse();
+ if (this.props.onChange) {
+ this.props.onChange(key);
+ }
+ };
+
+ const keys = this.props.keys || [];
+ const values = this.props.values || [];
+
+ const options = keys.map((key, i) =>
+
handler(key)}>{values[i]}
+ );
+
+ return (
+
+ {!inline && name &&
}
+
this.state.expanded ? collapse() : expand()}>
+
+ {((inline && name) ? (name + ": ") : "") + (this.props.value || "select a value")}
+
+
+ {options}
+
+
+
+ );
+ }
+}
+
+export default ChoiceField;
diff --git a/frontend/src/components/fields/ChoiceField.scss b/frontend/src/components/fields/ChoiceField.scss
new file mode 100644
index 0000000..7f32b0e
--- /dev/null
+++ b/frontend/src/components/fields/ChoiceField.scss
@@ -0,0 +1,69 @@
+@import '../../colors.scss';
+
+.choice-field {
+ font-size: 0.9em;
+
+ .field-name {
+ margin: 0;
+ }
+
+ .field-select {
+ position: relative;
+ margin-top: 5px;
+
+ .field-value {
+ background-color: $color-primary-2;
+ border: none;
+ color: $color-primary-4;
+ border-radius: 5px;
+ padding: 7px 10px;
+ cursor: pointer;
+
+ &:after {
+ content: "⋎";
+ position: absolute;
+ right: 10px;
+ }
+ }
+
+ .field-options {
+ position: absolute;
+ top: 35px;
+ width: 100%;
+ z-index: 20;
+ border-top: 3px solid $color-primary-0;
+ border-radius: 5px;
+ background-color: $color-primary-2;
+ display: none;
+
+ .field-option {
+ display: block;
+ padding: 5px 10px;
+ cursor: pointer;
+ border-radius: 5px;
+ }
+
+ .field-option:hover {
+ background-color: $color-primary-1;
+ }
+ }
+
+ &:focus {
+ outline: none;
+ }
+ }
+
+ .field-select.select-expanded {
+ .field-options {
+ display: block;
+ }
+
+ .field-value:after {
+ content: "⋏";
+ }
+ }
+
+ &.field-small {
+ font-size: 0.8em;
+ }
+}
diff --git a/frontend/src/components/fields/InputField.js b/frontend/src/components/fields/InputField.js
index af3b3df..6cf967a 100644
--- a/frontend/src/components/fields/InputField.js
+++ b/frontend/src/components/fields/InputField.js
@@ -1,5 +1,6 @@
import React, {Component} from 'react';
import './InputField.scss';
+import './common.scss';
import {randomClassName} from "../../utils";
const classNames = require('classnames');
@@ -36,12 +37,12 @@ class InputField extends Component {
};
let inputProps = {};
if (type !== "file") {
- inputProps["value"] = value;
+ inputProps["value"] = value || this.props.initialValue;
}
return (
-
+
{ name &&
diff --git a/frontend/src/components/fields/InputField.scss b/frontend/src/components/fields/InputField.scss
index cdb8c9f..79e2b7e 100644
--- a/frontend/src/components/fields/InputField.scss
+++ b/frontend/src/components/fields/InputField.scss
@@ -14,42 +14,20 @@
position: relative;
.field-value {
- input, .file-label {
+ .file-label {
background-color: $color-primary-2;
+ margin: 0;
width: 100%;
- border: none;
color: $color-primary-4;
border-radius: 5px;
padding: 7px 10px;
-
- &:focus {
- background-color: $color-primary-1;
- color: $color-primary-4;
- box-shadow: none;
- outline: none;
- }
-
- &[readonly] {
- background-color: $color-primary-2;
- border: none;
- color: $color-primary-4;
- }
-
- &[readonly]:focus {
- background-color: $color-primary-1;
- color: $color-primary-4;
- box-shadow: none;
- }
+ cursor: pointer;
}
input[type="file"] {
display: none;
}
- .file-label {
- margin: 0;
- }
-
.file-label:after {
content: "Browse";
position: absolute;
diff --git a/frontend/src/components/fields/TextField.js b/frontend/src/components/fields/TextField.js
index 86b98ed..de68c21 100644
--- a/frontend/src/components/fields/TextField.js
+++ b/frontend/src/components/fields/TextField.js
@@ -1,5 +1,6 @@
import React, {Component} from 'react';
import './TextField.scss';
+import './common.scss';
import {randomClassName} from "../../utils";
const classNames = require('classnames');
@@ -28,11 +29,11 @@ class TextField extends Component {
};
return (
-
{name &&
}
+ readOnly={this.props.readonly} value={this.props.value} ref={this.props.textRef} />
{error &&
error: {error}
}
);
diff --git a/frontend/src/components/fields/TextField.scss b/frontend/src/components/fields/TextField.scss
index 606f537..de831fb 100644
--- a/frontend/src/components/fields/TextField.scss
+++ b/frontend/src/components/fields/TextField.scss
@@ -10,32 +10,7 @@
}
textarea {
- background-color: $color-primary-2;
- width: 100%;
- border: none;
- color: $color-primary-4;
- border-radius: 5px;
- padding: 7px 10px;
resize: none;
-
- &:focus {
- background-color: $color-primary-1;
- color: $color-primary-4;
- box-shadow: none;
- outline: none;
- }
-
- &[readonly] {
- background-color: $color-primary-2;
- border: none;
- color: $color-primary-4;
- }
-
- &[readonly]:focus {
- background-color: $color-primary-1;
- color: $color-primary-4;
- box-shadow: none;
- }
}
&.field-active {
diff --git a/frontend/src/components/fields/common.scss b/frontend/src/components/fields/common.scss
new file mode 100644
index 0000000..f83a988
--- /dev/null
+++ b/frontend/src/components/fields/common.scss
@@ -0,0 +1,54 @@
+@import '../../colors.scss';
+
+.field {
+
+ input, textarea {
+ background-color: $color-primary-2;
+ width: 100%;
+ border: none;
+ color: $color-primary-4;
+ border-radius: 5px;
+ padding: 7px 10px;
+
+ &:focus {
+ background-color: $color-primary-1;
+ color: $color-primary-4;
+ box-shadow: none;
+ outline: none;
+ }
+
+ &[readonly] {
+ background-color: $color-primary-2;
+ border: none;
+ color: $color-primary-4;
+ }
+
+ &[readonly]:focus {
+ background-color: $color-primary-1;
+ color: $color-primary-4;
+ box-shadow: none;
+ }
+ }
+
+ button {
+ border-radius: 0;
+ background-color: $color-primary-2;
+ border: none;
+ color: $color-primary-4;
+ outline: none;
+ padding: 5px 12px;
+ font-weight: 500;
+
+ &:hover,
+ &:active {
+ background-color: $color-primary-1;
+ color: $color-primary-4;
+ }
+
+ &:focus,
+ &:active {
+ outline: none !important;
+ box-shadow: none !important;
+ }
+ }
+}
diff --git a/frontend/src/components/fields/extensions/ColorField.js b/frontend/src/components/fields/extensions/ColorField.js
new file mode 100644
index 0000000..edcb720
--- /dev/null
+++ b/frontend/src/components/fields/extensions/ColorField.js
@@ -0,0 +1,63 @@
+import React, {Component} from 'react';
+import {Button, ButtonGroup, Form, OverlayTrigger, Popover} from "react-bootstrap";
+import './ColorField.scss';
+import InputField from "../InputField";
+
+class ColorField extends Component {
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ invalid: false
+ };
+
+ this.colors = ["#E53935", "#D81B60", "#8E24AA", "#5E35B1", "#3949AB", "#1E88E5", "#039BE5", "#00ACC1",
+ "#00897B", "#43A047", "#7CB342", "#9E9D24", "#F9A825", "#FB8C00", "#F4511E", "#6D4C41"];
+ }
+
+ render() {
+ const colorButtons = this.colors.map((color) =>
+
{
+ this.setState({color: color});
+ if (typeof this.props.onChange === "function") {
+ this.props.onChange(color);
+ }
+ document.body.click(); // magic to close popup
+ }} />);
+
+ const popover = (
+
+ choose a color
+
+
+
+ {colorButtons.slice(0, 8)}
+
+
+ {colorButtons.slice(8, 18)}
+
+
+
+
+ );
+
+ return (
+
+ );
+ }
+
+}
+
+export default ColorField;
diff --git a/frontend/src/components/fields/extensions/ColorField.scss b/frontend/src/components/fields/extensions/ColorField.scss
new file mode 100644
index 0000000..c8f617c
--- /dev/null
+++ b/frontend/src/components/fields/extensions/ColorField.scss
@@ -0,0 +1,39 @@
+@import '../../../colors.scss';
+
+.color-field {
+ display: flex;
+ align-items: flex-end;
+
+ .input-field {
+ flex: 1;
+
+ input {
+ border-bottom-right-radius: 0 !important;
+ border-top-right-radius: 0 !important;
+ }
+ }
+
+ .color-picker {
+ margin-bottom: 5.5px;
+
+ .btn-picker {
+ padding: 8.5px 15px;
+ border-bottom-right-radius: 5px;
+ border-top-right-radius: 5px;
+ background-color: $color-indigo;
+ }
+ }
+
+
+}
+
+.colors-container {
+ width: 600px;
+
+ .color-input {
+ display: inline-block;
+ width: 31px;
+ height: 31px;
+ cursor: pointer;
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/components/fields/extensions/NumericField.js b/frontend/src/components/fields/extensions/NumericField.js
new file mode 100644
index 0000000..8823c42
--- /dev/null
+++ b/frontend/src/components/fields/extensions/NumericField.js
@@ -0,0 +1,39 @@
+import React, {Component} from 'react';
+import InputField from "../InputField";
+
+class NumericField extends Component {
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ invalid: false
+ };
+ }
+
+ render() {
+ const handler = (value) => {
+ value = value.replace(/[^\d]/gi, '');
+ let intValue = 0;
+ if (value !== "") {
+ intValue = parseInt(value);
+ }
+ const valid =
+ (!this.props.validate || (typeof this.props.validate === "function" && this.props.validate(intValue))) &&
+ (!this.props.min || (typeof this.props.min === "number" && intValue >= this.props.min)) &&
+ (!this.props.max || (typeof this.props.max === "number" && intValue <= this.props.max));
+ this.setState({invalid: !valid});
+ if (this.props.onChange) {
+ this.props.onChange(intValue);
+ }
+ };
+
+ return (
+
+ );
+ }
+
+}
+
+export default NumericField;
diff --git a/frontend/src/components/filters/RulesConnectionsFilter.js b/frontend/src/components/filters/RulesConnectionsFilter.js
index 621b6d6..0741bea 100644
--- a/frontend/src/components/filters/RulesConnectionsFilter.js
+++ b/frontend/src/components/filters/RulesConnectionsFilter.js
@@ -24,7 +24,7 @@ class RulesConnectionsFilter extends Component {
let params = new URLSearchParams(this.props.location.search);
let activeRules = params.getAll("matched_rules") || [];
- backend.get("/api/rules").then(res => {
+ backend.getJson("/api/rules").then(res => {
let rules = res.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});
diff --git a/frontend/src/components/panels/PcapPane.js b/frontend/src/components/panels/PcapPane.js
index 701edf2..7e0fa6c 100644
--- a/frontend/src/components/panels/PcapPane.js
+++ b/frontend/src/components/panels/PcapPane.js
@@ -3,7 +3,7 @@ import './PcapPane.scss';
import Table from "react-bootstrap/Table";
import backend from "../../backend";
import {createCurlCommand, formatSize, timestampToTime2} from "../../utils";
-import {Button, Col, Container, Form, Row} from "react-bootstrap";
+import {Button, Col, Container, Row} from "react-bootstrap";
import InputField from "../fields/InputField";
import CheckField from "../fields/CheckField";
import TextField from "../fields/TextField";
@@ -15,45 +15,48 @@ class PcapPane extends Component {
this.state = {
sessions: [],
- isFileValid: true,
- isFileFocused: false,
- selectedFile: null,
+ isUploadFileValid: true,
+ isUploadFileFocused: false,
+ uploadSelectedFile: null,
uploadFlushAll: false,
uploadStatusCode: null,
- uploadOutput: null
+ uploadOutput: null,
+ isFileValid: true,
+ isFileFocused: false,
+ fileValue: "",
+ fileFlushAll: false,
+ fileStatusCode: null,
+ fileOutput: null,
+ deleteOriginalFile: false
};
-
- this.loadSessions = this.loadSessions.bind(this);
- this.handleFileChange = this.handleFileChange.bind(this);
- this.handleUploadPcap = this.handleUploadPcap.bind(this);
}
componentDidMount() {
this.loadSessions();
}
- loadSessions() {
- backend.get("/api/pcap/sessions").then(res => this.setState({sessions: res}));
- }
+ loadSessions = () => {
+ backend.getJson("/api/pcap/sessions").then(res => this.setState({sessions: res}));
+ };
- handleFileChange(file) {
+ handleUploadFileChange = (file) => {
this.setState({
- isFileValid: file != null && file.type.endsWith("pcap"),
- isFileFocused: false,
- selectedFile: file
+ isUploadFileValid: file != null && file.type.endsWith("pcap"),
+ isUploadFileFocused: false,
+ uploadSelectedFile: file
});
- }
+ };
- handleUploadPcap() {
- if (this.state.selectedFile == null || !this.state.isFileValid) {
- this.setState({isFileFocused: true});
+ handleUploadPcap = () => {
+ if (this.state.uploadSelectedFile == null || !this.state.isUploadFileValid) {
+ this.setState({isUploadFileFocused: true});
return;
}
const formData = new FormData();
formData.append(
"file",
- this.state.selectedFile
+ this.state.uploadSelectedFile
);
backend.postFile("/api/pcap/upload", formData).then(response =>
@@ -62,7 +65,33 @@ class PcapPane extends Component {
uploadOutput: JSON.stringify(result)
}))
);
- }
+ };
+
+ handleFileChange = (file) => {
+ this.setState({
+ isFileValid: file !== "" && file.endsWith("pcap"),
+ isFileFocused: false,
+ fileValue: file
+ });
+ };
+
+ handleProcessPcap = () => {
+ 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.fileFlushAll,
+ delete_original_file: this.state.deleteOriginalFile
+ }).then(response =>
+ response.json().then(result => this.setState({
+ fileStatusCode: response.status + " " + response.statusText,
+ fileOutput: JSON.stringify(result)
+ }))
+ );
+ };
render() {
let sessions = this.state.sessions.map(s =>
@@ -82,7 +111,7 @@ class PcapPane extends Component {
const uploadOutput = this.state.uploadOutput != null ? this.state.uploadOutput :
createCurlCommand("pcap/upload", "POST", null, {
- file: "@" + ((this.state.selectedFile != null && this.state.isFileValid) ? this.state.selectedFile.name :
+ file: "@" + ((this.state.uploadSelectedFile != null && this.state.isUploadFileValid) ? this.state.uploadSelectedFile.name :
"invalid.pcap"),
flush_all: this.state.uploadFlushAll
})
@@ -120,17 +149,17 @@ class PcapPane extends Component {
-
+
POST /api/pcap/upload
{this.state.uploadStatusCode}
-
+
@@ -148,24 +177,31 @@ class PcapPane extends Component {
POST /api/pcap/file
-
+ {this.state.fileStatusCode}
-
+
+
+
+
+ this.setState({uploadFlushAll: v})}/>
+ this.setState({uploadFlushAll: v})}/>
+
+
+
+
+
-
-
-
-
);
}
}
diff --git a/frontend/src/components/panels/RulePane.js b/frontend/src/components/panels/RulePane.js
new file mode 100644
index 0000000..fbc8785
--- /dev/null
+++ b/frontend/src/components/panels/RulePane.js
@@ -0,0 +1,166 @@
+import React, {Component} from 'react';
+import './RulePane.scss';
+import Table from "react-bootstrap/Table";
+import {Button, 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";
+
+class RulePane extends Component {
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ rules: [],
+ };
+ }
+
+ componentDidMount() {
+ this.loadRules();
+ }
+
+ loadRules = () => {
+ backend.getJson("/api/rules").then(res => this.setState({rules: res}));
+ };
+
+
+ render() {
+ let rules = this.state.rules.map(r =>
+
+ {r["id"].substring(0, 8)} |
+ {r["name"]} |
+ {r["notes"]} |
+ {/*{((new Date(s["completed_at"]) - new Date(s["started_at"])) / 1000).toFixed(3)}s | */}
+ {/*{formatSize(s["size"])} | */}
+ {/*{s["processed_packets"]} | */}
+ {/*{s["invalid_packets"]} | */}
+ {/*undefined | */}
+ {/*download*/}
+ {/* | */}
+
+ );
+
+ return (
+
+
+
+ GET /api/rules
+ 200 OK
+
+
+
+
+
+
+ id |
+ name |
+ notes |
+
+
+
+ {rules}
+
+
+
+
+
+
+
+ POST /api/rules
+
+
+
+
+
+
+
+
+ this.setState({test1: e})} inline />
+
+
+
+
+ filters:
+ this.setState({test: e})} validate={(e) => e%2 === 0} />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ patterns:
+
+
+
+
+ regex |
+ Aa |
+ .* |
+ \n+ |
+ UTF8 |
+ Uni_ |
+ min |
+ max |
+ direction |
+ actions |
+
+
+
+
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ s", "s->c"]} value="both" /> |
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+}
+
+export default RulePane;
diff --git a/frontend/src/components/panels/RulePane.scss b/frontend/src/components/panels/RulePane.scss
new file mode 100644
index 0000000..b030c6a
--- /dev/null
+++ b/frontend/src/components/panels/RulePane.scss
@@ -0,0 +1,16 @@
+
+.rule-pane {
+ .post-rules-actions {
+ display: flex;
+
+ .rules-options {
+ flex: 1;
+ }
+
+ button {
+ margin-left: 10px;
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/frontend/src/index.scss b/frontend/src/index.scss
index 5d1bbfa..9dcc692 100644
--- a/frontend/src/index.scss
+++ b/frontend/src/index.scss
@@ -21,118 +21,6 @@ pre {
font-size: 14px;
}
-.btn {
- border-radius: 0;
- background-color: $color-primary-2;
- border: none;
- border-bottom: 5px solid $color-primary-1;
- color: $color-primary-4;
- outline: none;
- padding: 5px 12px;
- font-weight: 500;
-
- &:hover,
- &:active {
- background-color: $color-primary-1;
- color: $color-primary-4;
- }
-
- &:focus,
- &:active {
- outline: none !important;
- box-shadow: none !important;
- }
-}
-
-.btn-sm {
- border: none;
- font-size: 12px;
-}
-
-.btn-red {
- color: $color-red-light;
- background-color: $color-red;
- border-bottom: 5px solid $color-red-dark;
-
- &:hover,
- &:active {
- color: $color-red-light;
- background-color: $color-red-dark;
- }
-}
-
-.btn-pink {
- color: $color-pink-light;
- background-color: $color-pink;
- border-bottom: 5px solid $color-pink-dark;
-
- &:hover,
- &:active {
- color: $color-pink-light;
- background-color: $color-pink-dark;
- }
-}
-
-.btn-purple {
- color: $color-purple-light;
- background-color: $color-purple;
- border-bottom: 5px solid $color-purple-dark;
-
- &:hover,
- &:active {
- color: $color-purple-light;
- background-color: $color-purple-dark;
- }
-}
-
-.btn-deep-purple {
- color: $color-deep-purple-light;
- background-color: $color-deep-purple;
- border-bottom: 5px solid $color-deep-purple-dark;
-
- &:hover,
- &:active {
- color: $color-deep-purple-light;
- background-color: $color-deep-purple-dark;
- }
-}
-
-.btn-indigo {
- color: $color-indigo-light;
- background-color: $color-indigo;
- border-bottom: 5px solid $color-indigo-dark;
-
- &:hover,
- &:active {
- color: $color-indigo-light;
- background-color: $color-indigo-dark;
- }
-}
-
-.btn-blue {
- color: $color-blue-light;
- background-color: $color-blue;
- border-bottom: 5px solid $color-blue-dark;
-
- &:hover,
- &:active {
- color: $color-blue-light;
- background-color: $color-blue-dark;
- }
-}
-
-.btn-green {
- color: $color-green-light;
- background-color: $color-green;
- border-bottom: 5px solid $color-green-dark;
-
- &:hover,
- &:active {
- color: $color-green-light;
- background-color: $color-green-dark;
- }
-}
-
a {
color: $color-primary-4;
@@ -190,3 +78,7 @@ textarea.form-control {
.text-muted {
color: $color-primary-4 !important;
}
+
+.popover-header {
+ color: $color-primary-1;
+}
\ No newline at end of file
diff --git a/frontend/src/views/Connections.js b/frontend/src/views/Connections.js
index da8958b..f3fec64 100644
--- a/frontend/src/views/Connections.js
+++ b/frontend/src/views/Connections.js
@@ -25,21 +25,21 @@ class Connections extends Component {
this.scrollBottomThreashold = 0.99999;
this.maxConnections = 500;
this.queryLimit = 50;
-
- this.handleScroll = this.handleScroll.bind(this);
- this.connectionSelected = this.connectionSelected.bind(this);
- this.addServicePortFilter = this.addServicePortFilter.bind(this);
}
componentDidMount() {
this.loadConnections({limit: this.queryLimit})
.then(() => this.setState({loaded: true}));
+ if (this.props.initialConnection != null) {
+ this.setState({selected: this.props.initialConnection.id});
+ // TODO: scroll to initial connection
+ }
}
- connectionSelected(c) {
+ connectionSelected = (c) => {
this.setState({selected: c.id});
this.props.onSelected(c);
- }
+ };
componentDidUpdate(prevProps, prevState, snapshot) {
if (this.state.loaded && prevProps.location.search !== this.props.location.search) {
@@ -49,7 +49,7 @@ class Connections extends Component {
}
}
- handleScroll(e) {
+ handleScroll = (e) => {
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,})
@@ -59,13 +59,13 @@ class Connections extends Component {
this.loadConnections({to: this.state.firstConnection.id, limit: this.queryLimit,})
.then(() => console.log("Previous connections loaded"));
}
- }
+ };
- addServicePortFilter(port) {
+ addServicePortFilter = (port) => {
let urlParams = new URLSearchParams(this.props.location.search);
urlParams.set("service_port", port);
this.setState({queryString: "?" + urlParams});
- }
+ };
async loadConnections(params) {
let url = "/api/connections";
@@ -75,7 +75,7 @@ class Connections extends Component {
}
this.setState({loading: true, prevParams: params});
- let res = await backend.get(`${url}?${urlParams}`);
+ let res = await backend.getJson(`${url}?${urlParams}`);
let connections = this.state.connections;
let firstConnection = this.state.firstConnection;
@@ -115,7 +115,7 @@ class Connections extends Component {
let flagRule = this.state.flagRule;
let rules = this.state.rules;
if (flagRule === null) {
- rules = await backend.get("/api/rules");
+ rules = await backend.getJson("/api/rules");
flagRule = rules.filter(rule => {
return rule.name === "flag";
})[0];
diff --git a/frontend/src/views/Header.js b/frontend/src/views/Header.js
index e2d0e6a..a022636 100644
--- a/frontend/src/views/Header.js
+++ b/frontend/src/views/Header.js
@@ -72,7 +72,9 @@ class Header extends Component {
-
+
+
+
diff --git a/frontend/src/views/MainPane.js b/frontend/src/views/MainPane.js
index ce755d1..9d3f7b7 100644
--- a/frontend/src/views/MainPane.js
+++ b/frontend/src/views/MainPane.js
@@ -5,24 +5,25 @@ import ConnectionContent from "../components/ConnectionContent";
import {Route, Switch, withRouter} from "react-router-dom";
import PcapPane from "../components/panels/PcapPane";
import backend from "../backend";
+import RulePane from "../components/panels/RulePane";
class MainPane extends Component {
constructor(props) {
super(props);
this.state = {
- selectedConnection: null
+ selectedConnection: null,
+ loading: false
};
}
componentDidMount() {
- if ('id' in this.props.match.params) {
- const id = this.props.match.params.id;
- backend.get(`/api/connections/${id}`).then(res => {
- if (res.status === 200) {
- this.setState({selectedConnection: res});
- }
- });
+ const match = this.props.location.pathname.match(/^\/connections\/([a-f0-9]{24})$/);
+ if (match != null) {
+ this.setState({loading: true});
+ backend.getJson(`/api/connections/${match[1]}`)
+ .then(connection => this.setState({selectedConnection: connection, loading: false}))
+ .catch(error => console.log(error));
}
}
@@ -32,12 +33,18 @@ class MainPane extends Component {
- this.setState({selectedConnection: c})} />
+ {
+ !this.state.loading &&
+ this.setState({selectedConnection: c})}
+ initialConnection={this.state.selectedConnection} />
+ }
} />
- } />
+ } />
+ } />
+ } />
--
cgit v1.2.3-70-g09d2