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
--
cgit v1.2.3-70-g09d2