From d994a21a0dfae9ee026e8aa3ccdee6c213c523aa Mon Sep 17 00:00:00 2001
From: Emiliano Ciavatta
Date: Tue, 29 Sep 2020 18:56:00 +0200
Subject: Complete rules page
---
frontend/src/components/panels/RulePane.js | 388 ++++++++++++++++++++++++-----
1 file changed, 319 insertions(+), 69 deletions(-)
(limited to 'frontend/src/components/panels/RulePane.js')
diff --git a/frontend/src/components/panels/RulePane.js b/frontend/src/components/panels/RulePane.js
index 2e91d91..01e37fa 100644
--- a/frontend/src/components/panels/RulePane.js
+++ b/frontend/src/components/panels/RulePane.js
@@ -1,4 +1,5 @@
import React, {Component} from 'react';
+import './common.scss';
import './RulePane.scss';
import Table from "react-bootstrap/Table";
import {Col, Container, Row} from "react-bootstrap";
@@ -10,6 +11,11 @@ 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";
+
+const classNames = require('classnames');
+const _ = require('lodash');
class RulePane extends Component {
@@ -18,103 +24,352 @@ class RulePane extends Component {
this.state = {
rules: [],
+ newRule: this.emptyRule,
+ newPattern: this.emptyPattern
+ };
+
+ this.directions = {
+ 0: "both",
+ 1: "c->s",
+ 2: "s->c"
};
}
componentDidMount() {
+ this.reset();
this.loadRules();
}
+ 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
+ };
+
loadRules = () => {
- backend.getJson("/api/rules").then(res => this.setState({rules: res}));
+ backend.getJson("/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.postJson("/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.putJson(`/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"]} |
- {/*{((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*/}
- {/* | */}
+
+ );
+
+ 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
- 200 OK
+ {this.state.rulesStatusCode &&
+ }
-
-
-
-
- id |
- name |
- notes |
-
-
-
- {rules}
-
-
+
+
+
+
+
+ id |
+ name |
+ color |
+ notes |
+
+
+
+ {rules}
+
+
+
-
+
- POST /api/rules
-
+
+ {isUpdate ? `PUT /api/rules/${this.state.selectedRule.id}` : "POST /api/rules"}
+
+
-
- this.setState({test1: e})} inline />
-
+ this.updateParam((r) => r.name = v)}
+ error={this.state.ruleNameError} />
+ this.updateParam((r) => r.color = v)} />
+ this.updateParam((r) => r.notes = v)} />
-
- filters:
- this.setState({test: e})} validate={(e) => e%2 === 0} />
-
-
-
+
+ filters:
+ this.updateParam((r) => r.filter.service_port = v)}
+ min={0} max={65565} error={this.state.ruleServicePortError} />
+ this.updateParam((r) => r.filter.client_port = v)}
+ min={0} max={65565} error={this.state.ruleClientPortError} />
+ 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)} />
-
-
- patterns:
@@ -128,29 +383,24 @@ class RulePane extends Component {
min |
max |
direction |
- actions |
+ {!isUpdate && actions | }
-
- |
- |
- |
- |
- |
- |
- |
- |
- s", "s->c"]} value="both" /> |
- |
-
+ {patterns}
+ {this.state.rulePatternsError != null &&
+
error: {this.state.rulePatternsError}}
-
-
+
+ {}
+
+
+
);
}
--
cgit v1.2.3-70-g09d2