{ name &&
-
+
}
+ aria-describedby={this.id} onChange={handler} value={value} />
{ value !== "" &&
diff --git a/frontend/src/components/fields/StringField.scss b/frontend/src/components/fields/StringField.scss
index 674815f..2523c8d 100644
--- a/frontend/src/components/fields/StringField.scss
+++ b/frontend/src/components/fields/StringField.scss
@@ -1,6 +1,6 @@
@import '../../colors.scss';
-.field {
+.string-field {
font-size: 0.9em;
.field-name {
diff --git a/frontend/src/components/filters/BooleanConnectionsFilter.js b/frontend/src/components/filters/BooleanConnectionsFilter.js
index 7dea7cf..490d185 100644
--- a/frontend/src/components/filters/BooleanConnectionsFilter.js
+++ b/frontend/src/components/filters/BooleanConnectionsFilter.js
@@ -1,9 +1,7 @@
import React, {Component} from 'react';
import {withRouter} from "react-router-dom";
import {Redirect} from "react-router";
-import './BooleanConnectionsFilter.scss';
-
-const classNames = require('classnames');
+import BooleanField from "../fields/BooleanField";
class BooleanConnectionsFilter extends Component {
@@ -57,11 +55,9 @@ class BooleanConnectionsFilter extends Component {
}
return (
-
-
- {this.props.filterName}
-
-
+
+
{redirect}
);
diff --git a/frontend/src/components/filters/BooleanConnectionsFilter.scss b/frontend/src/components/filters/BooleanConnectionsFilter.scss
deleted file mode 100644
index 941b967..0000000
--- a/frontend/src/components/filters/BooleanConnectionsFilter.scss
+++ /dev/null
@@ -1,24 +0,0 @@
-@import '../../colors';
-
-.filter {
- .filter-boolean {
- padding: 0 10px;
- background-color: $color-primary-2;
- border-radius: 5px;
- cursor: pointer;
- height: 34px;
-
- span {
- display: block;
- font-size: 13px;
- padding: 6px 5px;
- }
- }
-
- &.filter-active {
- .filter-boolean {
- background-color: $color-primary-4;
- color: $color-primary-3;
- }
- }
-}
diff --git a/frontend/src/components/filters/FiltersDefinitions.js b/frontend/src/components/filters/FiltersDefinitions.js
index d36792e..02ccb42 100644
--- a/frontend/src/components/filters/FiltersDefinitions.js
+++ b/frontend/src/components/filters/FiltersDefinitions.js
@@ -12,7 +12,6 @@ import React from "react";
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"];
@@ -22,59 +21,70 @@ export const filtersDefinitions = {
defaultFilterValue="all_ports"
replaceFunc={cleanNumber}
validateFunc={validatePort}
- key="service_port_filter" />,
+ key="service_port_filter"
+ width={200} />,
matched_rules:
,
client_address:
,
+ key="client_address_filter"
+ width={320} />,
client_port:
,
+ key="client_port_filter"
+ width={200} />,
min_duration:
,
+ key="min_duration_filter"
+ width={200} />,
max_duration:
,
+ key="max_duration_filter"
+ width={200} />,
min_bytes:
,
+ key="min_bytes_filter"
+ width={200} />,
max_bytes:
,
+ key="max_bytes_filter"
+ width={200} />,
started_after:
,
+ key="started_after_filter"
+ width={200} />,
started_before:
,
+ key="started_before_filter"
+ width={200} />,
closed_after:
,
+ key="closed_after_filter"
+ width={200} />,
closed_before:
,
+ key="closed_before_filter"
+ width={200} />,
marked:
,
hidden:
};
diff --git a/frontend/src/components/filters/StringConnectionsFilter.js b/frontend/src/components/filters/StringConnectionsFilter.js
index 490a569..0d7f063 100644
--- a/frontend/src/components/filters/StringConnectionsFilter.js
+++ b/frontend/src/components/filters/StringConnectionsFilter.js
@@ -1,9 +1,7 @@
import React, {Component} from 'react';
import {withRouter} from "react-router-dom";
import {Redirect} from "react-router";
-import './StringConnectionsFilter.scss';
-
-const classNames = require('classnames');
+import StringField from "../fields/StringField";
class StringConnectionsFilter extends Component {
@@ -62,8 +60,7 @@ class StringConnectionsFilter extends Component {
(typeof this.props.validateFunc === "function" && this.props.validateFunc(value));
}
- filterChanged(event) {
- let fieldValue = event.target.value;
+ filterChanged(fieldValue) {
if (this.state.timeoutHandle !== null) {
clearTimeout(this.state.timeoutHandle);
}
@@ -116,26 +113,10 @@ class StringConnectionsFilter extends Component {
let active = this.state.filterValue !== null;
return (
-
-
-
- {this.props.filterName}:
-
-
-
-
- { active &&
-
- {
- this.needRedirect = true;
- this.setState({fieldValue: "", filterValue: null});
- }}>del
-
- }
-
+
+
{redirect}
);
diff --git a/frontend/src/components/filters/StringConnectionsFilter.scss b/frontend/src/components/filters/StringConnectionsFilter.scss
deleted file mode 100644
index 1476616..0000000
--- a/frontend/src/components/filters/StringConnectionsFilter.scss
+++ /dev/null
@@ -1,66 +0,0 @@
-@import '../../colors';
-
-.filter {
- margin: 0 10px;
- position: relative;
-
- .filter-name-wrapper {
- background-color: $color-primary-2;
- padding: 3px 7px;
- border-top-left-radius: 5px;
- border-bottom-left-radius: 5px;
- }
-
- .filter-name {
- font-size: 13px;
- }
-
- .filter-value {
- font-size: 13px;
- padding-left: 0;
- border-radius: 5px;
-
- &:focus {
- background-color: $color-primary-2;
- }
- }
-
- &.filter-active {
- .filter-name-wrapper {
- background-color: $color-primary-4;
- color: $color-primary-3;
- }
-
- .filter-value {
- background-color: $color-primary-4;
- color: $color-primary-3;
- }
- }
-
- &.filter-invalid {
- .filter-name-wrapper {
- background-color: $color-secondary-2;
- color: $color-primary-4;
- }
-
- .filter-value {
- background-color: $color-secondary-2;
- color: $color-primary-4;
- }
- }
-
- .filter-delete {
- position: absolute;
- right: 10px;
- top: 10px;
- z-index: 10;
- font-size: 11px;
- letter-spacing: -0.5px;
- color: $color-primary-2;
- cursor: pointer;
-
- .filter-delete-icon {
- font-weight: 800;
- }
- }
-}
diff --git a/frontend/src/components/panels/PcapPane.js b/frontend/src/components/panels/PcapPane.js
index 817c7b5..9f3bc19 100644
--- a/frontend/src/components/panels/PcapPane.js
+++ b/frontend/src/components/panels/PcapPane.js
@@ -5,6 +5,7 @@ import backend from "../../backend";
import {formatSize, timestampToTime2} from "../../utils";
import {Container, Row, Col, Form} from "react-bootstrap";
import StringField from "../fields/StringField";
+import BooleanField from "../fields/BooleanField";
class PcapPane extends Component {
@@ -13,6 +14,7 @@ class PcapPane extends Component {
this.state = {
sessions: [],
+ test: false
};
this.loadSessions = this.loadSessions.bind(this);
@@ -86,7 +88,7 @@ class PcapPane extends Component {
-
+
this.setState({test: v})} />
diff --git a/frontend/src/index.scss b/frontend/src/index.scss
index 358fd70..5d1bbfa 100644
--- a/frontend/src/index.scss
+++ b/frontend/src/index.scss
@@ -13,6 +13,7 @@ body {
color: $color-primary-4;
height: 100%;
max-height: 100%;
+ font-size: 100%;
}
pre {
@@ -182,32 +183,6 @@ textarea.form-control {
resize: none;
}
-input.form-control,
-textarea.form-control {
- background-color: $color-primary-2;
- border: none;
- color: $color-primary-4;
- font-family: 'Fira Code', monospace;
-
- &:focus {
- background-color: $color-primary-1;
- color: $color-primary-4;
- box-shadow: 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;
- }
-}
-
.table {
color: $color-primary-4;
}
diff --git a/frontend/src/utils.js b/frontend/src/utils.js
index c81cdfc..46667d6 100644
--- a/frontend/src/utils.js
+++ b/frontend/src/utils.js
@@ -83,6 +83,6 @@ export function formatSize(size) {
}
}
-export function randomClassName(size = 10) {
- return Math.random().toString(36).substring(size);
+export function randomClassName() {
+ return Math.random().toString(36).slice(2)
}
diff --git a/frontend/src/views/Header.js b/frontend/src/views/Header.js
index 5860d80..deb3ab7 100644
--- a/frontend/src/views/Header.js
+++ b/frontend/src/views/Header.js
@@ -60,10 +60,8 @@ class Header extends Component {
-
-
- {quickFilters}
-
+
+ {quickFilters}
diff --git a/frontend/src/views/Header.scss b/frontend/src/views/Header.scss
index e36b2d6..f3bfec1 100644
--- a/frontend/src/views/Header.scss
+++ b/frontend/src/views/Header.scss
@@ -22,8 +22,12 @@
}
}
- .filters-bar-wrapper {
- height: 50px;
+ .filters-bar {
padding: 8px 0;
+
+ .filter {
+ display: inline-block;
+ margin-right: 10px;
+ }
}
}
diff --git a/frontend/src/views/MainPane.js b/frontend/src/views/MainPane.js
index 3c0d795..88af4a7 100644
--- a/frontend/src/views/MainPane.js
+++ b/frontend/src/views/MainPane.js
@@ -35,8 +35,8 @@ class MainPane extends Component {
this.setState({selectedConnection: c})} />
diff --git a/frontend/yarn.lock b/frontend/yarn.lock
index 39d346b..c5f59cb 100644
--- a/frontend/yarn.lock
+++ b/frontend/yarn.lock
@@ -1691,9 +1691,9 @@
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
"@types/node@*":
- version "14.10.2"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-14.10.2.tgz#9b47a2c8e4dabd4db73b57e750b24af689600514"
- integrity sha512-IzMhbDYCpv26pC2wboJ4MMOa9GKtjplXfcAqrMeNJpUUwpM/2ATt2w1JPUXwS6spu856TvKZL2AOmeU2rAxskw==
+ version "14.11.1"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.1.tgz#56af902ad157e763f9ba63d671c39cda3193c835"
+ integrity sha512-oTQgnd0hblfLsJ6BvJzzSL+Inogp3lq9fGgqRkMB/ziKMgEUaFl801OncOzUmalfzt14N0oPHMK47ipl+wbTIw==
"@types/parse-json@^4.0.0":
version "4.0.0"
@@ -2792,20 +2792,19 @@ browserslist@4.10.0:
pkg-up "^3.1.0"
browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.6.2, browserslist@^4.6.4, browserslist@^4.8.5, browserslist@^4.9.1:
- version "4.14.2"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.14.2.tgz#1b3cec458a1ba87588cc5e9be62f19b6d48813ce"
- integrity sha512-HI4lPveGKUR0x2StIz+2FXfDk9SfVMrxn6PLh1JeGUwcuoDkdKZebWiyLRJ68iIPDpMI4JLVDf7S7XzslgWOhw==
+ version "4.14.3"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.14.3.tgz#381f9e7f13794b2eb17e1761b4f118e8ae665a53"
+ integrity sha512-GcZPC5+YqyPO4SFnz48/B0YaCwS47Q9iPChRGi6t7HhflKBcINzFrJvRfC+jp30sRMKxF+d4EHGs27Z0XP1NaQ==
dependencies:
- caniuse-lite "^1.0.30001125"
- electron-to-chromium "^1.3.564"
- escalade "^3.0.2"
+ caniuse-lite "^1.0.30001131"
+ electron-to-chromium "^1.3.570"
+ escalade "^3.1.0"
node-releases "^1.1.61"
bs-custom-file-input@^1.3.4:
version "1.3.4"
resolved "https://registry.yarnpkg.com/bs-custom-file-input/-/bs-custom-file-input-1.3.4.tgz#c275cb8d4f1c02ba026324292509fa9a747dbda8"
integrity sha512-NBsQzTnef3OW1MvdKBbMHAYHssCd613MSeJV7z2McXznWtVMnJCy7Ckyc+PwxV6Pk16cu6YBcYWh/ZE0XWNKCA==
->>>>>>> 98355cdf838d8c18e9a28176ae7a847770545395
bser@2.1.1:
version "2.1.1"
@@ -2983,10 +2982,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
-caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001035, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125:
- version "1.0.30001131"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001131.tgz#afad8a28fc2b7a0d3ae9407e71085a0ead905d54"
- integrity sha512-4QYi6Mal4MMfQMSqGIRPGbKIbZygeN83QsWq1ixpUwvtfgAZot5BrCKzGygvZaV+CnELdTwD0S4cqUNozq7/Cw==
+caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001035, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001131:
+ version "1.0.30001133"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001133.tgz#ec564c5495311299eb05245e252d589a84acd95e"
+ integrity sha512-s3XAUFaC/ntDb1O3lcw9K8MPeOW7KO3z9+GzAoBxfz1B0VdacXPMKgFUtG4KIsgmnbexmi013s9miVu4h+qMHw==
capture-exit@^2.0.0:
version "2.0.0"
@@ -3828,11 +3827,11 @@ debug@^3.1.1, debug@^3.2.5:
ms "^2.1.1"
debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
- integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1"
+ integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==
dependencies:
- ms "^2.1.1"
+ ms "2.1.2"
decamelize@^1.1.2, decamelize@^1.2.0:
version "1.2.0"
@@ -4152,7 +4151,7 @@ ee-first@1.1.1:
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
-electron-to-chromium@^1.3.378, electron-to-chromium@^1.3.564:
+electron-to-chromium@^1.3.378, electron-to-chromium@^1.3.570:
version "1.3.570"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.570.tgz#3f5141cc39b4e3892a276b4889980dabf1d29c7f"
integrity sha512-Y6OCoVQgFQBP5py6A/06+yWxUZHDlNr/gNDGatjH8AZqXl8X0tE4LfjLJsXGz/JmWJz8a6K7bR1k+QzZ+k//fg==
@@ -4305,7 +4304,7 @@ es6-symbol@^3.1.1, es6-symbol@~3.1.3:
d "^1.0.1"
ext "^1.1.2"
-escalade@^3.0.2:
+escalade@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.0.tgz#e8e2d7c7a8b76f6ee64c2181d6b8151441602d4e"
integrity sha512-mAk+hPSO8fLDkhV7V0dXazH5pDc6MrjBTPyD3VeKzxnVFjH1MIxbCdqGZB9O8+EwWakZs3ZCbDS4IpRt79V1ig==
@@ -7377,7 +7376,7 @@ ms@2.1.1:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
-ms@^2.1.1:
+ms@2.1.2, ms@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
@@ -8470,9 +8469,9 @@ postcss-lab-function@^2.0.1:
postcss-values-parser "^2.0.0"
postcss-load-config@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.0.tgz#c84d692b7bb7b41ddced94ee62e8ab31b417b003"
- integrity sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.1.tgz#0a684bb8beb05e55baf922f7ab44c3edb17cf78e"
+ integrity sha512-D2ENobdoZsW0+BHy4x1CAkXtbXtYWYRIxL/JbtRBqrRGOPtJ2zoga/bEZWhV/ShWB5saVxJMzbMdSyA/vv4tXw==
dependencies:
cosmiconfig "^5.0.0"
import-cwd "^2.0.0"
@@ -8846,13 +8845,14 @@ postcss-selector-parser@^5.0.0-rc.3, postcss-selector-parser@^5.0.0-rc.4:
uniq "^1.0.1"
postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2:
- version "6.0.2"
- resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c"
- integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==
+ version "6.0.3"
+ resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.3.tgz#766d77728728817cc140fa1ac6da5e77f9fada98"
+ integrity sha512-0ClFaY4X1ra21LRqbW6y3rUbWcxnSVkDFG57R7Nxus9J9myPFlv+jYDMohzpkBx0RrjjiqjtycpchQ+PLGmZ9w==
dependencies:
cssesc "^3.0.0"
indexes-of "^1.0.1"
uniq "^1.0.1"
+ util-deprecate "^1.0.2"
postcss-svgo@^4.0.2:
version "4.0.2"
@@ -8902,9 +8902,9 @@ postcss@7.0.21:
supports-color "^6.1.0"
postcss@^7, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.23, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6:
- version "7.0.32"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.32.tgz#4310d6ee347053da3433db2be492883d62cec59d"
- integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==
+ version "7.0.34"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.34.tgz#f2baf57c36010df7de4009940f21532c16d65c20"
+ integrity sha512-H/7V2VeNScX9KE83GDrDZNiGT1m2H+UTnlinIzhjlLX9hfMUn1mHNnGeX81a1c8JSBdBvqk7c2ZOG6ZPn5itGw==
dependencies:
chalk "^2.4.2"
source-map "^0.6.1"
@@ -9572,9 +9572,9 @@ regexpp@^3.0.0:
integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==
regexpu-core@^4.7.0:
- version "4.7.0"
- resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.0.tgz#fcbf458c50431b0bb7b45d6967b8192d91f3d938"
- integrity sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==
+ version "4.7.1"
+ resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6"
+ integrity sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==
dependencies:
regenerate "^1.4.0"
regenerate-unicode-properties "^8.2.0"
@@ -10307,9 +10307,9 @@ spdx-expression-parse@^3.0.0:
spdx-license-ids "^3.0.0"
spdx-license-ids@^3.0.0:
- version "3.0.5"
- resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654"
- integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz#c80757383c28abf7296744998cbc106ae8b854ce"
+ integrity sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==
spdy-transport@^3.0.0:
version "3.0.0"
@@ -11124,7 +11124,7 @@ use@^3.1.0:
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
-util-deprecate@^1.0.1, util-deprecate@~1.0.1:
+util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
--
cgit v1.2.3-70-g09d2
From 44af615b32faf53c04cd38cb63782cf1b1332c94 Mon Sep 17 00:00:00 2001
From: Emiliano Ciavatta
Date: Sat, 26 Sep 2020 12:05:27 +0200
Subject: General refactor
---
frontend/package.json | 1 -
frontend/src/backend.js | 38 ++--
frontend/src/components/fields/BooleanField.js | 37 ----
frontend/src/components/fields/BooleanField.scss | 34 ----
frontend/src/components/fields/CheckField.js | 36 ++++
frontend/src/components/fields/CheckField.scss | 35 ++++
frontend/src/components/fields/InputField.js | 75 +++++++
frontend/src/components/fields/InputField.scss | 147 ++++++++++++++
frontend/src/components/fields/StringField.js | 66 -------
frontend/src/components/fields/StringField.scss | 117 -----------
frontend/src/components/fields/TextField.js | 42 ++++
frontend/src/components/fields/TextField.scss | 79 ++++++++
.../components/filters/BooleanConnectionsFilter.js | 6 +-
.../components/filters/StringConnectionsFilter.js | 8 +-
frontend/src/components/panels/PcapPane.js | 86 ++++++--
frontend/src/components/panels/PcapPane.scss | 14 +-
frontend/src/utils.js | 18 +-
frontend/src/views/App.js | 12 +-
frontend/src/views/Header.js | 5 +-
frontend/src/views/Header.scss | 2 +-
frontend/src/views/MainPane.js | 8 +-
frontend/src/views/Services.js | 2 +-
frontend/src/views/Upload.js | 218 ---------------------
frontend/src/views/Upload.scss | 21 --
24 files changed, 555 insertions(+), 552 deletions(-)
delete mode 100644 frontend/src/components/fields/BooleanField.js
delete mode 100644 frontend/src/components/fields/BooleanField.scss
create mode 100644 frontend/src/components/fields/CheckField.js
create mode 100644 frontend/src/components/fields/CheckField.scss
create mode 100644 frontend/src/components/fields/InputField.js
create mode 100644 frontend/src/components/fields/InputField.scss
delete mode 100644 frontend/src/components/fields/StringField.js
delete mode 100644 frontend/src/components/fields/StringField.scss
create mode 100644 frontend/src/components/fields/TextField.js
create mode 100644 frontend/src/components/fields/TextField.scss
delete mode 100644 frontend/src/views/Upload.js
delete mode 100644 frontend/src/views/Upload.scss
(limited to 'frontend/src/components/fields')
diff --git a/frontend/package.json b/frontend/package.json
index bf995c5..3629e70 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -9,7 +9,6 @@
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
- "axios": "^0.19.2",
"bootstrap": "^4.4.1",
"bs-custom-file-input": "^1.3.4",
"classnames": "^2.2.6",
diff --git a/frontend/src/backend.js b/frontend/src/backend.js
index 35ae6e3..5eb0e40 100644
--- a/frontend/src/backend.js
+++ b/frontend/src/backend.js
@@ -1,11 +1,11 @@
-async function request(method, url, data) {
+async function json(method, url, data, headers) {
const options = {
method: method,
mode: "cors",
cache: "no-cache",
credentials: "same-origin",
- headers: {
+ headers: headers || {
"Content-Type": "application/json"
},
redirect: "follow",
@@ -18,19 +18,35 @@ async function request(method, url, data) {
return result.json();
}
+async function file(url, data, headers) {
+ const options = {
+ method: "POST",
+ mode: "cors",
+ cache: "no-cache",
+ credentials: "same-origin",
+ body: data,
+ redirect: "follow",
+ referrerPolicy: "no-referrer",
+ };
+ return await fetch(url, options);
+}
+
const backend = {
- get: (url = "") => {
- return request("GET", url, null);
+ get: (url = "", headers = null) => {
+ return json("GET", url, null, headers);
},
- post: (url = "", data = null) => {
- return request("POST", url, data);
+ post: (url = "", data = null, headers = null) => {
+ return json("POST", url, data, headers);
},
- put: (url = "", data = null) => {
- return request("PUT", url, data);
+ put: (url = "", data = null, headers = null) => {
+ return json("PUT", url, data, headers);
+ },
+ delete: (url = "", data = null, headers = null) => {
+ return json("DELETE", url, data, headers);
+ },
+ postFile: (url = "", data = null, headers = null) => {
+ return file(url, data, headers);
},
- delete: (url = "", data = null) => {
- return request("DELETE", url, data);
- }
};
export default backend;
diff --git a/frontend/src/components/fields/BooleanField.js b/frontend/src/components/fields/BooleanField.js
deleted file mode 100644
index 06a6da7..0000000
--- a/frontend/src/components/fields/BooleanField.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import React, {Component} from 'react';
-import './BooleanField.scss';
-import {randomClassName} from "../../utils";
-
-const classNames = require('classnames');
-
-class BooleanField extends Component {
-
- constructor(props) {
- super(props);
-
- this.id = `field-${this.props.name || "noname"}-${randomClassName()}`;
- }
-
- render() {
-
- const checked = this.props.checked || false;
- const small = this.props.small || false;
- const name = this.props.name || null;
- const handler = () => {
- if (this.props.onChange) {
- this.props.onChange(!checked);
- }
- };
-
- return (
-
-
-
-
-
-
- );
- }
-}
-
-export default BooleanField;
diff --git a/frontend/src/components/fields/BooleanField.scss b/frontend/src/components/fields/BooleanField.scss
deleted file mode 100644
index 6ec25f7..0000000
--- a/frontend/src/components/fields/BooleanField.scss
+++ /dev/null
@@ -1,34 +0,0 @@
-@import '../../colors.scss';
-
-.boolean-field {
- font-size: 0.9em;
-
- .field-input {
- border-radius: 5px;
- width: fit-content;
- background-color: $color-primary-2;
-
- input {
- display: none;
- }
-
- label {
- margin: 0;
- padding: 6px 15px;
- cursor: pointer;
- }
-
- &:hover {
- background-color: $color-primary-1;
- }
- }
-
- &.field-checked .field-input {
- background-color: $color-primary-4 !important;
- color: $color-primary-3;
- }
-
- &.field-small {
- font-size: 0.8em;
- }
-}
diff --git a/frontend/src/components/fields/CheckField.js b/frontend/src/components/fields/CheckField.js
new file mode 100644
index 0000000..5cceac4
--- /dev/null
+++ b/frontend/src/components/fields/CheckField.js
@@ -0,0 +1,36 @@
+import React, {Component} from 'react';
+import './CheckField.scss';
+import {randomClassName} from "../../utils";
+
+const classNames = require('classnames');
+
+class CheckField extends Component {
+
+ constructor(props) {
+ super(props);
+
+ this.id = `field-${this.props.name || "noname"}-${randomClassName()}`;
+ }
+
+ render() {
+ const checked = this.props.checked || false;
+ const small = this.props.small || false;
+ const name = this.props.name || null;
+ const handler = () => {
+ if (this.props.onChange) {
+ this.props.onChange(!checked);
+ }
+ };
+
+ return (
+
+
+
+
+
+
+ );
+ }
+}
+
+export default CheckField;
diff --git a/frontend/src/components/fields/CheckField.scss b/frontend/src/components/fields/CheckField.scss
new file mode 100644
index 0000000..7b0ac5f
--- /dev/null
+++ b/frontend/src/components/fields/CheckField.scss
@@ -0,0 +1,35 @@
+@import '../../colors.scss';
+
+.check-field {
+ font-size: 0.9em;
+ margin: 5px 0;
+
+ .field-input {
+ border-radius: 5px;
+ width: fit-content;
+ background-color: $color-primary-2;
+
+ input {
+ display: none;
+ }
+
+ label {
+ margin: 0;
+ padding: 6px 15px;
+ cursor: pointer;
+ }
+
+ &:hover {
+ background-color: $color-primary-1;
+ }
+ }
+
+ &.field-checked .field-input {
+ background-color: $color-primary-4 !important;
+ color: $color-primary-3;
+ }
+
+ &.field-small {
+ font-size: 0.8em;
+ }
+}
diff --git a/frontend/src/components/fields/InputField.js b/frontend/src/components/fields/InputField.js
new file mode 100644
index 0000000..af3b3df
--- /dev/null
+++ b/frontend/src/components/fields/InputField.js
@@ -0,0 +1,75 @@
+import React, {Component} from 'react';
+import './InputField.scss';
+import {randomClassName} from "../../utils";
+
+const classNames = require('classnames');
+
+class InputField extends Component {
+
+ constructor(props) {
+ super(props);
+
+ this.id = `field-${this.props.name || "noname"}-${randomClassName()}`;
+ }
+
+ render() {
+ const active = this.props.active || false;
+ const invalid = this.props.invalid || false;
+ const small = this.props.small || false;
+ const inline = this.props.inline || false;
+ const name = this.props.name || null;
+ const value = this.props.value || "";
+ const type = this.props.type || "text";
+ const error = this.props.error || null;
+ const defaultValue = this.props.defaultValue || null;
+ const handler = (e) => {
+ if (this.props.onChange) {
+ if (type === "file") {
+ let file = e.target.files[0];
+ this.props.onChange(file);
+ } else if (e == null) {
+ this.props.onChange("");
+ } else {
+ this.props.onChange(e.target.value);
+ }
+ }
+ };
+ let inputProps = {};
+ if (type !== "file") {
+ inputProps["value"] = value;
+ }
+
+ return (
+
+
+ { name &&
+
+
+
+ }
+
+
+ { type === "file" && }
+
+
+ { type !== "file" && value !== "" &&
+
+ handler(null)}>del
+
+ }
+
+
+ {error &&
+
+ error: {error}
+
+ }
+
+ );
+ }
+}
+
+export default InputField;
diff --git a/frontend/src/components/fields/InputField.scss b/frontend/src/components/fields/InputField.scss
new file mode 100644
index 0000000..cdb8c9f
--- /dev/null
+++ b/frontend/src/components/fields/InputField.scss
@@ -0,0 +1,147 @@
+@import '../../colors.scss';
+
+.input-field {
+ font-size: 0.9em;
+ margin: 5px 0;
+
+ .field-name {
+ label {
+ margin: 0;
+ }
+ }
+
+ .field-input {
+ position: relative;
+
+ .field-value {
+ input, .file-label {
+ 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;
+ }
+ }
+
+ input[type="file"] {
+ display: none;
+ }
+
+ .file-label {
+ margin: 0;
+ }
+
+ .file-label:after {
+ content: "Browse";
+ position: absolute;
+ right: 0;
+ top: 0;
+ padding: 7px 10px 7px 12px;
+ background-color: $color-primary-1;
+ border-bottom-right-radius: 5px;
+ border-top-right-radius: 5px;
+ }
+ }
+ }
+
+ &.field-active {
+ &.field-inline .field-name {
+ background-color: $color-primary-4 !important;
+ color: $color-primary-3 !important;
+ }
+
+ .field-value input, .field-value .file-label {
+ background-color: $color-primary-4 !important;
+ color: $color-primary-3 !important;
+ }
+
+ .file-label:after {
+ background-color: $color-secondary-4 !important;
+ }
+ }
+
+ &.field-invalid {
+ &.field-inline .field-name {
+ background-color: $color-secondary-2 !important;
+ color: $color-primary-4 !important;
+ }
+
+ .field-value input, .field-value .file-label {
+ background-color: $color-secondary-2 !important;
+ color: $color-primary-4 !important;
+ }
+
+ .file-label:after {
+ background-color: $color-secondary-1 !important;
+ }
+ }
+
+ &.field-small {
+ font-size: 0.8em;
+ }
+
+ &.field-inline .field-wrapper {
+ display: flex;
+
+ .field-name {
+ background-color: $color-primary-2;
+ padding: 6px 7px;
+ border-top-left-radius: 5px;
+ border-bottom-left-radius: 5px;
+ }
+
+ .field-input {
+ width: 100%;
+
+ input, .file-label {
+ border-bottom-left-radius: 0;
+ border-top-left-radius: 0;
+ padding-left: 3px;
+ }
+ }
+
+ &:focus-within .field-name {
+ background-color: $color-primary-1;
+ }
+ }
+
+ .field-clear {
+ position: absolute;
+ right: 8px;
+ top: 8px;
+ z-index: 10;
+ font-size: 0.9em;
+ font-weight: 600;
+ letter-spacing: -0.5px;
+ cursor: pointer;
+ }
+
+ &.field-active .field-clear {
+ color: $color-primary-2;
+ }
+
+ .field-error {
+ padding: 5px 10px;
+ font-size: 0.9em;
+ color: $color-secondary-0;
+ }
+}
diff --git a/frontend/src/components/fields/StringField.js b/frontend/src/components/fields/StringField.js
deleted file mode 100644
index 7781b2d..0000000
--- a/frontend/src/components/fields/StringField.js
+++ /dev/null
@@ -1,66 +0,0 @@
-import React, {Component} from 'react';
-import './StringField.scss';
-import {randomClassName} from "../../utils";
-
-const classNames = require('classnames');
-
-class StringField extends Component {
-
- constructor(props) {
- super(props);
-
- this.id = `field-${this.props.name || "noname"}-${randomClassName()}`;
- }
-
- render() {
-
- const active = this.props.active || false;
- const invalid = this.props.invalid || false;
- const small = this.props.small || false;
- const inline = this.props.inline || false;
- const name = this.props.name || null;
- const value = this.props.value || "";
- const type = this.props.type || "text";
- const error = this.props.error || null;
- const handler = (e) => {
- if (this.props.onChange) {
- if (e == null) {
- this.props.onChange("");
- } else {
- this.props.onChange(e.target.value);
- }
- }
- };
-
- return (
-
-
- { name &&
-
-
-
- }
-
-
-
-
- { value !== "" &&
-
- handler(null)}>del
-
- }
-
-
- {error &&
-
- error: {error}
-
- }
-
- );
- }
-}
-
-export default StringField;
diff --git a/frontend/src/components/fields/StringField.scss b/frontend/src/components/fields/StringField.scss
deleted file mode 100644
index 2523c8d..0000000
--- a/frontend/src/components/fields/StringField.scss
+++ /dev/null
@@ -1,117 +0,0 @@
-@import '../../colors.scss';
-
-.string-field {
- font-size: 0.9em;
-
- .field-name {
- label {
- margin: 0;
- }
- }
-
- .field-input {
- position: relative;
-
- .field-value input {
- 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;
- }
- }
- }
-
- &.field-active {
- &.field-inline .field-name {
- background-color: $color-primary-4 !important;
- color: $color-primary-3 !important;
- }
-
- .field-value input {
- background-color: $color-primary-4 !important;
- color: $color-primary-3 !important;
- }
- }
-
- &.field-invalid {
- &.field-inline .field-name {
- background-color: $color-secondary-2 !important;
- color: $color-primary-4 !important;
- }
-
- .field-value input {
- background-color: $color-secondary-2 !important;
- color: $color-primary-4 !important;
- }
- }
-
- &.field-small {
- font-size: 0.8em;
- }
-
- &.field-inline .field-wrapper {
- display: flex;
-
- .field-name {
- background-color: $color-primary-2;
- padding: 6px 7px;
- border-top-left-radius: 5px;
- border-bottom-left-radius: 5px;
- }
-
- .field-input {
- width: 100%;
-
- input {
- border-bottom-left-radius: 0;
- border-top-left-radius: 0;
- padding-left: 3px;
- }
- }
-
- &:focus-within .field-name {
- background-color: $color-primary-1;
- }
- }
-
- .field-clear {
- position: absolute;
- right: 8px;
- top: 8px;
- z-index: 10;
- font-size: 0.9em;
- font-weight: 600;
- letter-spacing: -0.5px;
- cursor: pointer;
- }
-
- &.field-active .field-clear {
- color: $color-primary-2;
- }
-
- .field-error {
- padding: 5px 10px;
- font-size: 0.9em;
- color: $color-secondary-0;
- }
-}
diff --git a/frontend/src/components/fields/TextField.js b/frontend/src/components/fields/TextField.js
new file mode 100644
index 0000000..86b98ed
--- /dev/null
+++ b/frontend/src/components/fields/TextField.js
@@ -0,0 +1,42 @@
+import React, {Component} from 'react';
+import './TextField.scss';
+import {randomClassName} from "../../utils";
+
+const classNames = require('classnames');
+
+class TextField extends Component {
+
+ constructor(props) {
+ super(props);
+
+ this.id = `field-${this.props.name || "noname"}-${randomClassName()}`;
+ }
+
+ render() {
+ const name = this.props.name || null;
+ const error = this.props.error || null;
+ const rows = this.props.rows || 3;
+
+ const handler = (e) => {
+ if (this.props.onChange) {
+ if (e == null) {
+ this.props.onChange("");
+ } else {
+ this.props.onChange(e.target.value);
+ }
+ }
+ };
+
+ return (
+
+ {name &&
}
+
+ {error &&
error: {error}
}
+
+ );
+ }
+}
+
+export default TextField;
diff --git a/frontend/src/components/fields/TextField.scss b/frontend/src/components/fields/TextField.scss
new file mode 100644
index 0000000..606f537
--- /dev/null
+++ b/frontend/src/components/fields/TextField.scss
@@ -0,0 +1,79 @@
+@import '../../colors.scss';
+
+.text-field {
+ font-size: 0.9em;
+ margin: 5px 0;
+
+ label {
+ display: block;
+ margin: 0;
+ }
+
+ 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 {
+ textarea {
+ background-color: $color-primary-4 !important;
+ color: $color-primary-3 !important;
+ }
+ }
+
+ &.field-invalid {
+ textarea {
+ background-color: $color-secondary-2 !important;
+ color: $color-primary-4 !important;
+ }
+ }
+
+ &.field-small {
+ font-size: 0.8em;
+ }
+
+ .field-clear {
+ position: absolute;
+ right: 8px;
+ top: 8px;
+ z-index: 10;
+ font-size: 0.9em;
+ font-weight: 600;
+ letter-spacing: -0.5px;
+ cursor: pointer;
+ }
+
+ &.field-active .field-clear {
+ color: $color-primary-2;
+ }
+
+ .field-error {
+ padding: 5px 10px;
+ font-size: 0.9em;
+ color: $color-secondary-0;
+ }
+}
diff --git a/frontend/src/components/filters/BooleanConnectionsFilter.js b/frontend/src/components/filters/BooleanConnectionsFilter.js
index 490d185..4c5a78a 100644
--- a/frontend/src/components/filters/BooleanConnectionsFilter.js
+++ b/frontend/src/components/filters/BooleanConnectionsFilter.js
@@ -1,7 +1,7 @@
import React, {Component} from 'react';
import {withRouter} from "react-router-dom";
import {Redirect} from "react-router";
-import BooleanField from "../fields/BooleanField";
+import CheckField from "../fields/CheckField";
class BooleanConnectionsFilter extends Component {
@@ -56,8 +56,8 @@ class BooleanConnectionsFilter extends Component {
return (
-
+
{redirect}
);
diff --git a/frontend/src/components/filters/StringConnectionsFilter.js b/frontend/src/components/filters/StringConnectionsFilter.js
index 0d7f063..a304198 100644
--- a/frontend/src/components/filters/StringConnectionsFilter.js
+++ b/frontend/src/components/filters/StringConnectionsFilter.js
@@ -1,7 +1,7 @@
import React, {Component} from 'react';
import {withRouter} from "react-router-dom";
import {Redirect} from "react-router";
-import StringField from "../fields/StringField";
+import InputField from "../fields/InputField";
class StringConnectionsFilter extends Component {
@@ -114,9 +114,9 @@ class StringConnectionsFilter extends Component {
return (
-
+
{redirect}
);
diff --git a/frontend/src/components/panels/PcapPane.js b/frontend/src/components/panels/PcapPane.js
index 9f3bc19..701edf2 100644
--- a/frontend/src/components/panels/PcapPane.js
+++ b/frontend/src/components/panels/PcapPane.js
@@ -2,10 +2,11 @@ import React, {Component} from 'react';
import './PcapPane.scss';
import Table from "react-bootstrap/Table";
import backend from "../../backend";
-import {formatSize, timestampToTime2} from "../../utils";
-import {Container, Row, Col, Form} from "react-bootstrap";
-import StringField from "../fields/StringField";
-import BooleanField from "../fields/BooleanField";
+import {createCurlCommand, formatSize, timestampToTime2} from "../../utils";
+import {Button, Col, Container, Form, Row} from "react-bootstrap";
+import InputField from "../fields/InputField";
+import CheckField from "../fields/CheckField";
+import TextField from "../fields/TextField";
class PcapPane extends Component {
@@ -14,10 +15,17 @@ class PcapPane extends Component {
this.state = {
sessions: [],
- test: false
+ isFileValid: true,
+ isFileFocused: false,
+ selectedFile: null,
+ uploadFlushAll: false,
+ uploadStatusCode: null,
+ uploadOutput: null
};
this.loadSessions = this.loadSessions.bind(this);
+ this.handleFileChange = this.handleFileChange.bind(this);
+ this.handleUploadPcap = this.handleUploadPcap.bind(this);
}
componentDidMount() {
@@ -28,6 +36,34 @@ class PcapPane extends Component {
backend.get("/api/pcap/sessions").then(res => this.setState({sessions: res}));
}
+ handleFileChange(file) {
+ this.setState({
+ isFileValid: file != null && file.type.endsWith("pcap"),
+ isFileFocused: false,
+ selectedFile: file
+ });
+ }
+
+ handleUploadPcap() {
+ if (this.state.selectedFile == null || !this.state.isFileValid) {
+ this.setState({isFileFocused: true});
+ return;
+ }
+
+ const formData = new FormData();
+ formData.append(
+ "file",
+ this.state.selectedFile
+ );
+
+ backend.postFile("/api/pcap/upload", formData).then(response =>
+ response.json().then(result => this.setState({
+ uploadStatusCode: response.status + " " + response.statusText,
+ uploadOutput: JSON.stringify(result)
+ }))
+ );
+ }
+
render() {
let sessions = this.state.sessions.map(s =>
@@ -38,10 +74,20 @@ class PcapPane extends Component {
{s["processed_packets"]} |
{s["invalid_packets"]} |
undefined |
- download |
+ download
+ |
);
+ 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 :
+ "invalid.pcap"),
+ flush_all: this.state.uploadFlushAll
+ })
+ ;
+
return (
@@ -77,19 +123,25 @@ class PcapPane extends Component {
POST /api/pcap/upload
-
+ {this.state.uploadStatusCode}
-
-
-
-
-
this.setState({test: v})} />
-
+
+
+
+
+ options:
+ this.setState({uploadFlushAll: v})}/>
+
+
+
+
+
@@ -110,8 +162,6 @@ class PcapPane extends Component {
-
-
diff --git a/frontend/src/components/panels/PcapPane.scss b/frontend/src/components/panels/PcapPane.scss
index 3df87f5..ce28227 100644
--- a/frontend/src/components/panels/PcapPane.scss
+++ b/frontend/src/components/panels/PcapPane.scss
@@ -38,8 +38,6 @@
padding: 10px;
}
-
-
th {
background-color: $color-primary-2;
border-top: 3px solid $color-primary-3;
@@ -50,6 +48,18 @@
padding: 5px;
}
+ .upload-actions {
+ display: flex;
+ align-items: flex-end;
+ margin-bottom: 20px;
+ }
+
+ .upload-options {
+ flex: 1;
+ span {
+ font-size: 0.9em;
+ }
+ }
}
\ No newline at end of file
diff --git a/frontend/src/utils.js b/frontend/src/utils.js
index 46667d6..6a5411c 100644
--- a/frontend/src/utils.js
+++ b/frontend/src/utils.js
@@ -1,9 +1,19 @@
const timeRegex = /^(0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/;
-export function createCurlCommand(subCommand, data) {
- let full = window.location.protocol + '//' + window.location.hostname + (window.location.port ? ':' + window.location.port : '');
- return `curl --request PUT \\\n --url ${full}/api${subCommand} \\\n ` +
- `--header 'content-type: application/json' \\\n --data '${JSON.stringify(data)}'`;
+export function createCurlCommand(subCommand, method = null, json = null, data = null) {
+ 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';
+ content = ` -d '${JSON.stringify(json)}'`;
+ } else if (data != null) {
+ contentType = ' -H "Content-Type: multipart/form-data" \\\n';
+ content = " " + Object.entries(data).map(([key, value]) => `-F "${key}=${value}"`).join(" \\\n ");
+ }
+
+ return `curl${method != null ? " -X " + method : ""} "${full}/api${subCommand}" \\\n` + contentType + "" + content;
}
export function validateIpAddress(ipAddress) {
diff --git a/frontend/src/views/App.js b/frontend/src/views/App.js
index ebead2f..ccfdb3a 100644
--- a/frontend/src/views/App.js
+++ b/frontend/src/views/App.js
@@ -2,12 +2,11 @@ import React, {Component} from 'react';
import Header from "./Header";
import MainPane from "./MainPane";
import Footer from "./Footer";
-import {BrowserRouter as Router, Route, Switch} from "react-router-dom";
+import {BrowserRouter as Router} from "react-router-dom";
import Services from "./Services";
import Filters from "./Filters";
import Rules from "./Rules";
import Config from "./Config";
-import Upload from "./Upload";
class App extends Component {
@@ -18,7 +17,6 @@ class App extends Component {
filterWindowOpen: false,
rulesWindowOpen: false,
configWindowOpen: false,
- uploadWindowOpen: false,
configDone: false
};
@@ -49,9 +47,6 @@ class App extends Component {
modal =
this.setState({configWindowOpen: false})}
onDone={() => this.setState({configDone: true})}/>;
}
- if (this.state.uploadWindowOpen) {
- modal = this.setState({uploadWindowOpen: false}) }/>;
- }
return (
@@ -63,10 +58,7 @@ class App extends Component {
onOpenUpload={() => this.setState({uploadWindowOpen: true})}
onConfigDone={this.state.configDone}
/>
-
- }/>
- }/>
-
+
{modal}
diff --git a/frontend/src/views/Header.js b/frontend/src/views/Header.js
index deb3ab7..e2d0e6a 100644
--- a/frontend/src/views/Header.js
+++ b/frontend/src/views/Header.js
@@ -3,6 +3,7 @@ import Typed from 'typed.js';
import './Header.scss';
import {Button} from "react-bootstrap";
import {filtersDefinitions, filtersNames} from "../components/filters/FiltersDefinitions";
+import {Link} from "react-router-dom";
class Header extends Component {
@@ -68,7 +69,9 @@ class Header extends Component {
-
+
+
+
-
- {/*
*/}
+
+ } />
+ } />
+
diff --git a/frontend/src/views/Services.js b/frontend/src/views/Services.js
index 22d61b3..97368dc 100644
--- a/frontend/src/views/Services.js
+++ b/frontend/src/views/Services.js
@@ -102,7 +102,7 @@ class Services extends Component {
output += "assert(len(name) >= 3)\n";
}
if (output === "") {
- output = createCurlCommand("/services", {
+ output = createCurlCommand("/services", "PUT", {
"port": this.state.port,
"name": this.state.name,
"color": this.state.color,
diff --git a/frontend/src/views/Upload.js b/frontend/src/views/Upload.js
deleted file mode 100644
index 29d514d..0000000
--- a/frontend/src/views/Upload.js
+++ /dev/null
@@ -1,218 +0,0 @@
-import React, {Component} from 'react';
-import './Upload.scss';
-import {Button, ButtonGroup, ToggleButton, Col, Container, Form, FormFile, InputGroup, Modal, Row, Table} from "react-bootstrap";
-import bsCustomFileInput from 'bs-custom-file-input'
-import {createCurlCommand} from '../utils';
-
-class Upload extends Component {
-
- constructor(props) {
- super(props);
-
- this.state = {
- selectedFile: null,
- removeOriginal: false,
- flushAll: false,
- errors: ""
- };
-
- this.flushAllChanged = this.flushAllChanged.bind(this);
- this.removeOriginalChanged = this.removeOriginalChanged.bind(this);
-
-
- }
-
- flushAllChanged() {
- this.setState({flushAll: !this.value});
- this.checked = !this.checked;
- this.value = !this.value;
- }
-
- removeOriginalChanged() {
- this.setState({removeOriginal: !this.value});
- this.checked = !this.checked;
- this.value = !this.value;
- }
-
- onLocalFileChange = event => {
- this.setState({ selectedFile: event.target.value });
-
- };
-
- onFileChange = event => {
- this.setState({ selectedFile: event.target.files[0] });
-
- };
-
- componentDidMount() {
- bsCustomFileInput.init()
- }
-
- onFileProcess = () => {
- const data = {
- "file": this.state.selectedFile,
- "flush_all": this.state.flushAll,
- "delete_original_file": this.state.removeOriginal};
-
- fetch('/api/pcap/file', {
- method: 'POST',
- body: JSON.stringify(data)
- })
- .then(response => {
- if (response.status === 202 ){
- this.props.onHide();
- } else {
- response.json().then(data => {
- this.setState(
- {errors : data.error.toString()}
- );
- });
- }
- }
- );
- }
-
- onFileUpload = () => {
- const formData = new FormData();
- formData.append(
- "file",
- this.state.selectedFile,
- this.state.selectedFile.name
- );
- fetch('/api/pcap/upload', {
- method: 'POST',
- body: formData
- })
- .then(response => {
- if (response.status === 202 ){
- //this.setState({showConfig:false});
- this.props.onHide();
- //this.props.onDone();
- } else {
- response.json().then(data => {
- this.setState(
- {errors : data.error.toString()}
- );
- });
- }
- }
- );
- }
-
-
- render() {
-
- return (
-
-
-
- /usr/bin/load_pcap
-
-
-
-
-
-
- --local
-
-
- --upload
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- this.removeOriginalChanged()}
- >
- --remove-original-file
-
-
-
-
-
-
-
-
-
- this.flushAllChanged()}
- >
- --flush-all
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {this.state.errors
- .split('\n').map((item, key) => {
- return {item}
})
- }
-
-
-
-
-
-
-
-
-
-
- );
- }
-}
-
-export default Upload;
diff --git a/frontend/src/views/Upload.scss b/frontend/src/views/Upload.scss
deleted file mode 100644
index e327b8c..0000000
--- a/frontend/src/views/Upload.scss
+++ /dev/null
@@ -1,21 +0,0 @@
-@import '../colors.scss';
-
-.curl-output {
- width: 100%;
- font-size: 13px;
-}
-
-#pcap-upload{
- align: center;
- width: 100%;
-}
-
-.btn-color {
- border: 3px solid #fff;
-}
-
-.dialog-footer {
- .btn {
- width: 80px;
- }
-}
--
cgit v1.2.3-70-g09d2
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/src/components/fields')
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
From 7f4cc5d3f3f92338a464853c182b9d6a3ea850eb Mon Sep 17 00:00:00 2001
From: Emiliano Ciavatta
Date: Sun, 27 Sep 2020 18:43:27 +0200
Subject: Replaced the old fields with the new ones
---
frontend/src/components/Connection.js | 8 +--
frontend/src/components/ConnectionContent.js | 68 +++++-----------------
frontend/src/components/ConnectionContent.scss | 12 ++--
frontend/src/components/MessageAction.js | 4 +-
frontend/src/components/fields/ButtonField.js | 9 +++
frontend/src/components/fields/ButtonField.scss | 4 ++
frontend/src/components/fields/CheckField.scss | 4 ++
frontend/src/components/fields/ChoiceField.js | 15 ++++-
frontend/src/components/fields/ChoiceField.scss | 2 +-
.../src/components/fields/extensions/ColorField.js | 17 +++---
.../components/fields/extensions/ColorField.scss | 9 ++-
frontend/src/components/panels/PcapPane.js | 7 ++-
frontend/src/components/panels/RulePane.js | 12 +---
frontend/src/views/Filters.js | 5 +-
frontend/src/views/Header.js | 13 +++--
frontend/src/views/Header.scss | 9 +--
16 files changed, 93 insertions(+), 105 deletions(-)
(limited to 'frontend/src/components/fields')
diff --git a/frontend/src/components/Connection.js b/frontend/src/components/Connection.js
index 1149584..7887185 100644
--- a/frontend/src/components/Connection.js
+++ b/frontend/src/components/Connection.js
@@ -1,8 +1,9 @@
import React, {Component} from 'react';
import './Connection.scss';
-import {Button, Form, OverlayTrigger, Popover} from "react-bootstrap";
+import {Form, OverlayTrigger, Popover} from "react-bootstrap";
import backend from "../backend";
import {formatSize} from "../utils";
+import ButtonField from "./fields/ButtonField";
class Connection extends Component {
@@ -96,9 +97,8 @@ class Connection extends Component {
-
+ this.props.addServicePortFilter(conn.port_dst)} />
|
{conn.ip_src} |
diff --git a/frontend/src/components/ConnectionContent.js b/frontend/src/components/ConnectionContent.js
index 0069424..318965c 100644
--- a/frontend/src/components/ConnectionContent.js
+++ b/frontend/src/components/ConnectionContent.js
@@ -1,8 +1,10 @@
import React, {Component} from 'react';
import './ConnectionContent.scss';
-import {Button, Dropdown, Row} from 'react-bootstrap';
+import {Dropdown, Row} from 'react-bootstrap';
import MessageAction from "./MessageAction";
import backend from "../backend";
+import ButtonField from "./fields/ButtonField";
+import ChoiceField from "./fields/ChoiceField";
const classNames = require('classnames');
@@ -93,12 +95,12 @@ class ConnectionContent extends Component {
}
return Object.entries(connectionMessage.metadata["reproducers"]).map(([actionName, actionValue]) =>
-
+ }} />
);
}
@@ -138,56 +140,16 @@ class ConnectionContent extends Component {
| timestamp: {this.props.connection.started_at}
-
-
- format
-
-
-
- plain
- hex
- hexdump
- base32
- base64
- ascii
- binary
- decimal
- octal
-
-
-
-
-
- view_as
-
-
-
- default
-
-
-
-
-
-
- download_as
-
-
-
- nl_separated
- only_client
- only_server
-
-
-
+
+
+
+
+
diff --git a/frontend/src/components/ConnectionContent.scss b/frontend/src/components/ConnectionContent.scss
index 8ee31ec..fde54c0 100644
--- a/frontend/src/components/ConnectionContent.scss
+++ b/frontend/src/components/ConnectionContent.scss
@@ -51,7 +51,7 @@
}
&:hover .connection-message-actions {
- display: block;
+ display: flex;
}
.connection-message-label {
@@ -89,17 +89,19 @@
.connection-content-header {
background-color: $color-primary-2;
padding: 0;
- height: 31px;
+ height: 33px;
.header-info {
- padding-top: 5px;
+ padding-top: 7px;
padding-left: 20px;
font-size: 12px;
}
.header-actions {
- .dropdown {
- display: inline-block;
+ display: flex;
+
+ .choice-field {
+ margin-top: -5px;
}
}
}
diff --git a/frontend/src/components/MessageAction.js b/frontend/src/components/MessageAction.js
index 2c6ebbc..8f4b031 100644
--- a/frontend/src/components/MessageAction.js
+++ b/frontend/src/components/MessageAction.js
@@ -40,8 +40,8 @@ class MessageAction extends Component {
-
-
+
+
);
diff --git a/frontend/src/components/fields/ButtonField.js b/frontend/src/components/fields/ButtonField.js
index b32aee8..f2f02fd 100644
--- a/frontend/src/components/fields/ButtonField.js
+++ b/frontend/src/components/fields/ButtonField.js
@@ -31,6 +31,15 @@ class ButtonField extends Component {
if (this.props.border) {
buttonStyle["borderColor"] = this.props.border;
}
+ if (this.props.fullSpan) {
+ buttonStyle["width"] = "100%";
+ }
+ if (this.props.rounded) {
+ buttonStyle["borderRadius"] = "3px";
+ }
+ if (this.props.inline) {
+ buttonStyle["marginTop"] = "8px";
+ }
return (
diff --git a/frontend/src/components/fields/ButtonField.scss b/frontend/src/components/fields/ButtonField.scss
index 933279e..aabe80f 100644
--- a/frontend/src/components/fields/ButtonField.scss
+++ b/frontend/src/components/fields/ButtonField.scss
@@ -9,6 +9,10 @@
&.field-small {
font-size: 0.8em;
+
+ button {
+ padding: 4px 12px;
+ }
}
.button-variant-red {
diff --git a/frontend/src/components/fields/CheckField.scss b/frontend/src/components/fields/CheckField.scss
index 7b0ac5f..ab932b4 100644
--- a/frontend/src/components/fields/CheckField.scss
+++ b/frontend/src/components/fields/CheckField.scss
@@ -31,5 +31,9 @@
&.field-small {
font-size: 0.8em;
+
+ .field-input label {
+ padding: 7px 15px;
+ }
}
}
diff --git a/frontend/src/components/fields/ChoiceField.js b/frontend/src/components/fields/ChoiceField.js
index d409b21..73e950d 100644
--- a/frontend/src/components/fields/ChoiceField.js
+++ b/frontend/src/components/fields/ChoiceField.js
@@ -38,15 +38,24 @@ class ChoiceField extends Component {
handler(key)}>{values[i]}
);
+ let fieldValue = "";
+ if (inline && name) {
+ fieldValue = name;
+ }
+ if (!this.props.onlyName && inline && name) {
+ fieldValue += ": ";
+ }
+ if (!this.props.onlyName) {
+ fieldValue += this.props.value || "select a value";
+ }
+
return (
{!inline && name &&
}
this.state.expanded ? collapse() : expand()}>
-
- {((inline && name) ? (name + ": ") : "") + (this.props.value || "select a value")}
-
+
{fieldValue}
{options}
diff --git a/frontend/src/components/fields/ChoiceField.scss b/frontend/src/components/fields/ChoiceField.scss
index 7f32b0e..e7683b7 100644
--- a/frontend/src/components/fields/ChoiceField.scss
+++ b/frontend/src/components/fields/ChoiceField.scss
@@ -16,7 +16,7 @@
border: none;
color: $color-primary-4;
border-radius: 5px;
- padding: 7px 10px;
+ padding: 7px 25px 7px 10px;
cursor: pointer;
&:after {
diff --git a/frontend/src/components/fields/extensions/ColorField.js b/frontend/src/components/fields/extensions/ColorField.js
index edcb720..c1c210f 100644
--- a/frontend/src/components/fields/extensions/ColorField.js
+++ b/frontend/src/components/fields/extensions/ColorField.js
@@ -1,5 +1,5 @@
import React, {Component} from 'react';
-import {Button, ButtonGroup, Form, OverlayTrigger, Popover} from "react-bootstrap";
+import {OverlayTrigger, Popover} from "react-bootstrap";
import './ColorField.scss';
import InputField from "../InputField";
@@ -9,7 +9,6 @@ class ColorField extends Component {
super(props);
this.state = {
- invalid: false
};
this.colors = ["#E53935", "#D81B60", "#8E24AA", "#5E35B1", "#3949AB", "#1E88E5", "#039BE5", "#00ACC1",
@@ -18,8 +17,7 @@ class ColorField extends Component {
render() {
const colorButtons = this.colors.map((color) =>
-
{
this.setState({color: color});
if (typeof this.props.onChange === "function") {
@@ -44,16 +42,19 @@ class ColorField extends Component {
);
+ let buttonStyles = {};
+ if (this.state.color) {
+ buttonStyles["backgroundColor"] = this.state.color;
+ }
+
return (
-
+
);
}
diff --git a/frontend/src/components/fields/extensions/ColorField.scss b/frontend/src/components/fields/extensions/ColorField.scss
index c8f617c..1c3931f 100644
--- a/frontend/src/components/fields/extensions/ColorField.scss
+++ b/frontend/src/components/fields/extensions/ColorField.scss
@@ -16,15 +16,14 @@
.color-picker {
margin-bottom: 5.5px;
- .btn-picker {
- padding: 8.5px 15px;
+ .picker-button {
+ font-size: 0.8em;
+ padding: 8px 15px;
border-bottom-right-radius: 5px;
border-top-right-radius: 5px;
- background-color: $color-indigo;
+ background-color: $color-primary-1;
}
}
-
-
}
.colors-container {
diff --git a/frontend/src/components/panels/PcapPane.js b/frontend/src/components/panels/PcapPane.js
index 7e0fa6c..a491dff 100644
--- a/frontend/src/components/panels/PcapPane.js
+++ b/frontend/src/components/panels/PcapPane.js
@@ -3,10 +3,11 @@ import './PcapPane.scss';
import Table from "react-bootstrap/Table";
import backend from "../../backend";
import {createCurlCommand, formatSize, timestampToTime2} from "../../utils";
-import {Button, Col, Container, Row} from "react-bootstrap";
+import {Col, Container, Row} from "react-bootstrap";
import InputField from "../fields/InputField";
import CheckField from "../fields/CheckField";
import TextField from "../fields/TextField";
+import ButtonField from "../fields/ButtonField";
class PcapPane extends Component {
@@ -167,7 +168,7 @@ class PcapPane extends Component {
this.setState({uploadFlushAll: v})}/>
-
+
@@ -192,7 +193,7 @@ class PcapPane extends Component {
this.setState({uploadFlushAll: v})}/>
-
+
diff --git a/frontend/src/components/panels/RulePane.js b/frontend/src/components/panels/RulePane.js
index fbc8785..2e91d91 100644
--- a/frontend/src/components/panels/RulePane.js
+++ b/frontend/src/components/panels/RulePane.js
@@ -1,7 +1,7 @@
import React, {Component} from 'react';
import './RulePane.scss';
import Table from "react-bootstrap/Table";
-import {Button, Col, Container, Row} from "react-bootstrap";
+import {Col, Container, Row} from "react-bootstrap";
import InputField from "../fields/InputField";
import CheckField from "../fields/CheckField";
import TextField from "../fields/TextField";
@@ -141,18 +141,12 @@ class RulePane extends Component {
|
|
|
- s", "s->c"]} value="both" /> |
- |
+ s", "s->c"]} value="both" /> |
+ |
-
-
-
-
-
-
diff --git a/frontend/src/views/Filters.js b/frontend/src/views/Filters.js
index 78f0342..b62e7eb 100644
--- a/frontend/src/views/Filters.js
+++ b/frontend/src/views/Filters.js
@@ -1,7 +1,8 @@
import React, {Component} from 'react';
import './Services.scss';
-import {Button, Col, Container, Modal, Row, Table} from "react-bootstrap";
+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 {
@@ -89,7 +90,7 @@ class Filters extends Component {
-
+
);
diff --git a/frontend/src/views/Header.js b/frontend/src/views/Header.js
index a022636..0b82011 100644
--- a/frontend/src/views/Header.js
+++ b/frontend/src/views/Header.js
@@ -4,6 +4,7 @@ import './Header.scss';
import {Button} from "react-bootstrap";
import {filtersDefinitions, filtersNames} from "../components/filters/FiltersDefinitions";
import {Link} from "react-router-dom";
+import ButtonField from "../components/fields/ButtonField";
class Header extends Component {
@@ -68,16 +69,16 @@ class Header extends Component {
-
+
-
+
-
+
-
-
+
+
diff --git a/frontend/src/views/Header.scss b/frontend/src/views/Header.scss
index 71d49e3..15d1375 100644
--- a/frontend/src/views/Header.scss
+++ b/frontend/src/views/Header.scss
@@ -14,11 +14,12 @@
}
.header-buttons {
- margin: 5px 0;
- text-align: right;
+ margin: 7px 0;
+ display: flex;
+ justify-content: flex-end;
- button {
- margin-left: 10px;
+ .button-field {
+ margin-left: 7px;
}
}
--
cgit v1.2.3-70-g09d2
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/package.json | 1 +
frontend/src/backend.js | 31 +-
frontend/src/components/ConnectionContent.js | 4 +-
frontend/src/components/fields/ButtonField.js | 4 -
frontend/src/components/fields/ButtonField.scss | 2 +-
frontend/src/components/fields/InputField.js | 13 +-
.../components/fields/extensions/NumericField.js | 40 ++-
.../components/filters/RulesConnectionsFilter.js | 2 +-
.../components/filters/StringConnectionsFilter.js | 2 +-
frontend/src/components/objects/LinkPopover.js | 33 ++
frontend/src/components/objects/LinkPopover.scss | 7 +
frontend/src/components/panels/PcapPane.js | 7 +-
frontend/src/components/panels/PcapPane.scss | 47 +--
frontend/src/components/panels/RulePane.js | 388 +++++++++++++++++----
frontend/src/components/panels/RulePane.scss | 30 +-
frontend/src/components/panels/common.scss | 85 +++++
frontend/src/validation.js | 8 +
frontend/src/views/App.js | 6 -
frontend/src/views/Config.js | 2 -
frontend/src/views/Connections.js | 4 +-
frontend/src/views/Header.js | 1 -
frontend/src/views/MainPane.js | 2 +-
frontend/src/views/Rules.js | 118 -------
23 files changed, 536 insertions(+), 301 deletions(-)
create mode 100644 frontend/src/components/objects/LinkPopover.js
create mode 100644 frontend/src/components/objects/LinkPopover.scss
create mode 100644 frontend/src/components/panels/common.scss
create mode 100644 frontend/src/validation.js
delete mode 100644 frontend/src/views/Rules.js
(limited to 'frontend/src/components/fields')
diff --git a/frontend/package.json b/frontend/package.json
index 3629e70..b13a7ea 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -13,6 +13,7 @@
"bs-custom-file-input": "^1.3.4",
"classnames": "^2.2.6",
"eslint-config-react-app": "^5.2.1",
+ "lodash": "^4.17.20",
"node-sass": "^4.14.0",
"react": "^16.13.1",
"react-bootstrap": "^1.0.1",
diff --git a/frontend/src/backend.js b/frontend/src/backend.js
index a02f7a8..2fbe920 100644
--- a/frontend/src/backend.js
+++ b/frontend/src/backend.js
@@ -1,5 +1,5 @@
-async function json(method, url, data, headers, returnJson) {
+async function json(method, url, data, headers) {
const options = {
method: method,
mode: "cors",
@@ -14,18 +14,17 @@ async function json(method, url, data, headers, returnJson) {
if (data != null) {
options.body = JSON.stringify(data);
}
- const result = await fetch(url, options);
- if (returnJson) {
- if (result.status >= 200 && result.status < 300) {
- return result.json();
- } else {
- return Promise.reject({
- response: result,
- json: await result.json()
- });
- }
- } else {
+ const response = await fetch(url, options);
+ const result = {
+ statusCode: response.status,
+ status: `${response.status} ${response.statusText}`,
+ json: await response.json()
+ };
+
+ if (response.status >= 200 && response.status < 300) {
return result;
+ } else {
+ return Promise.reject(result);
}
}
@@ -56,16 +55,16 @@ const backend = {
return json("DELETE", url, data, headers);
},
getJson: (url = "", headers = null) => {
- return json("GET", url, null, headers, true);
+ return json("GET", url, null, headers);
},
postJson: (url = "", data = null, headers = null) => {
- return json("POST", url, data, headers, true);
+ return json("POST", url, data, headers);
},
putJson: (url = "", data = null, headers = null) => {
- return json("PUT", url, data, headers, true);
+ return json("PUT", url, data, headers);
},
deleteJson: (url = "", data = null, headers = null) => {
- return json("DELETE", url, data, headers, true);
+ return json("DELETE", url, data, headers);
},
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 318965c..a9d34d3 100644
--- a/frontend/src/components/ConnectionContent.js
+++ b/frontend/src/components/ConnectionContent.js
@@ -1,6 +1,6 @@
import React, {Component} from 'react';
import './ConnectionContent.scss';
-import {Dropdown, Row} from 'react-bootstrap';
+import {Row} from 'react-bootstrap';
import MessageAction from "./MessageAction";
import backend from "../backend";
import ButtonField from "./fields/ButtonField";
@@ -42,7 +42,7 @@ class ConnectionContent extends Component {
// TODO: limit workaround.
backend.getJson(`/api/streams/${this.props.connection.id}?format=${this.state.format}&limit=999999`).then(res => {
this.setState({
- connectionContent: res,
+ connectionContent: res.json,
loading: false
});
});
diff --git a/frontend/src/components/fields/ButtonField.js b/frontend/src/components/fields/ButtonField.js
index f2f02fd..cc32b0f 100644
--- a/frontend/src/components/fields/ButtonField.js
+++ b/frontend/src/components/fields/ButtonField.js
@@ -6,10 +6,6 @@ const classNames = require('classnames');
class ButtonField extends Component {
- constructor(props) {
- super(props);
- }
-
render() {
const handler = () => {
if (typeof this.props.onClick === "function") {
diff --git a/frontend/src/components/fields/ButtonField.scss b/frontend/src/components/fields/ButtonField.scss
index aabe80f..cfd20ff 100644
--- a/frontend/src/components/fields/ButtonField.scss
+++ b/frontend/src/components/fields/ButtonField.scss
@@ -11,7 +11,7 @@
font-size: 0.8em;
button {
- padding: 4px 12px;
+ padding: 3px 12px;
}
}
diff --git a/frontend/src/components/fields/InputField.js b/frontend/src/components/fields/InputField.js
index 6cf967a..b790891 100644
--- a/frontend/src/components/fields/InputField.js
+++ b/frontend/src/components/fields/InputField.js
@@ -20,16 +20,17 @@ class InputField extends Component {
const inline = this.props.inline || false;
const name = this.props.name || null;
const value = this.props.value || "";
+ const defaultValue = this.props.defaultValue || "";
const type = this.props.type || "text";
const error = this.props.error || null;
- const defaultValue = this.props.defaultValue || null;
+
const handler = (e) => {
- if (this.props.onChange) {
+ if (typeof this.props.onChange === "function") {
if (type === "file") {
let file = e.target.files[0];
this.props.onChange(file);
} else if (e == null) {
- this.props.onChange("");
+ this.props.onChange(defaultValue);
} else {
this.props.onChange(e.target.value);
}
@@ -37,7 +38,7 @@ class InputField extends Component {
};
let inputProps = {};
if (type !== "file") {
- inputProps["value"] = value || this.props.initialValue;
+ inputProps["value"] = value || defaultValue;
}
return (
@@ -52,8 +53,8 @@ class InputField extends Component {
{ type === "file" && }
- }
+
{ type !== "file" && value !== "" &&
diff --git a/frontend/src/components/fields/extensions/NumericField.js b/frontend/src/components/fields/extensions/NumericField.js
index 8823c42..ed81ed7 100644
--- a/frontend/src/components/fields/extensions/NumericField.js
+++ b/frontend/src/components/fields/extensions/NumericField.js
@@ -11,25 +11,31 @@ class NumericField extends Component {
};
}
- 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);
- }
- };
+ componentDidUpdate(prevProps, prevState, snapshot) {
+ if (prevProps.value !== this.props.value) {
+ this.onChange(this.props.value);
+ }
+ }
+ onChange = (value) => {
+ value = value.toString().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 (typeof this.props.onChange === "function") {
+ this.props.onChange(intValue);
+ }
+ };
+
+ render() {
return (
-
);
}
diff --git a/frontend/src/components/filters/RulesConnectionsFilter.js b/frontend/src/components/filters/RulesConnectionsFilter.js
index 0741bea..f4d0b1c 100644
--- a/frontend/src/components/filters/RulesConnectionsFilter.js
+++ b/frontend/src/components/filters/RulesConnectionsFilter.js
@@ -25,7 +25,7 @@ class RulesConnectionsFilter extends Component {
let activeRules = params.getAll("matched_rules") || [];
backend.getJson("/api/rules").then(res => {
- let rules = res.flatMap(rule => rule.enabled ? [{id: rule.id, name: rule.name}] : []);
+ 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});
});
diff --git a/frontend/src/components/filters/StringConnectionsFilter.js b/frontend/src/components/filters/StringConnectionsFilter.js
index a304198..f463593 100644
--- a/frontend/src/components/filters/StringConnectionsFilter.js
+++ b/frontend/src/components/filters/StringConnectionsFilter.js
@@ -115,7 +115,7 @@ class StringConnectionsFilter extends Component {
return (
{redirect}
diff --git a/frontend/src/components/objects/LinkPopover.js b/frontend/src/components/objects/LinkPopover.js
new file mode 100644
index 0000000..58b2f6a
--- /dev/null
+++ b/frontend/src/components/objects/LinkPopover.js
@@ -0,0 +1,33 @@
+import React, {Component} from 'react';
+import {randomClassName} from "../../utils";
+import {OverlayTrigger, Popover} from "react-bootstrap";
+import './LinkPopover.scss';
+
+class LinkPopover extends Component {
+
+ constructor(props) {
+ super(props);
+
+ this.id = `link-overlay-${randomClassName()}`;
+ }
+
+ render() {
+ const popover = (
+
+ {this.props.title && {this.props.title}}
+
+ {this.props.content}
+
+
+ );
+
+ return (this.props.content ?
+
+ {this.props.text}
+ :
+
{this.props.text}
+ );
+ }
+}
+
+export default LinkPopover;
diff --git a/frontend/src/components/objects/LinkPopover.scss b/frontend/src/components/objects/LinkPopover.scss
new file mode 100644
index 0000000..d5f4879
--- /dev/null
+++ b/frontend/src/components/objects/LinkPopover.scss
@@ -0,0 +1,7 @@
+@import '../../colors.scss';
+
+.link-popover {
+ text-decoration: underline;
+ font-weight: 500;
+ cursor: pointer;
+}
\ No newline at end of file
diff --git a/frontend/src/components/panels/PcapPane.js b/frontend/src/components/panels/PcapPane.js
index a491dff..cfba037 100644
--- a/frontend/src/components/panels/PcapPane.js
+++ b/frontend/src/components/panels/PcapPane.js
@@ -1,5 +1,6 @@
import React, {Component} from 'react';
-import './PcapPane.scss';
+// import './PcapPane.scss';
+import './common.scss';
import Table from "react-bootstrap/Table";
import backend from "../../backend";
import {createCurlCommand, formatSize, timestampToTime2} from "../../utils";
@@ -37,7 +38,7 @@ class PcapPane extends Component {
}
loadSessions = () => {
- backend.getJson("/api/pcap/sessions").then(res => this.setState({sessions: res}));
+ backend.getJson("/api/pcap/sessions").then(res => this.setState({sessions: res.json}));
};
handleUploadFileChange = (file) => {
@@ -104,7 +105,7 @@ class PcapPane extends Component {
{s["processed_packets"]} |
{s["invalid_packets"]} |
undefined |
-
download
|
diff --git a/frontend/src/components/panels/PcapPane.scss b/frontend/src/components/panels/PcapPane.scss
index ce28227..3c4d10b 100644
--- a/frontend/src/components/panels/PcapPane.scss
+++ b/frontend/src/components/panels/PcapPane.scss
@@ -1,51 +1,10 @@
@import '../../colors.scss';
.pane-container {
- background-color: $color-primary-3;
- padding: 10px 10px 0;
- height: 100%;
- .section-header {
- background-color: $color-primary-2;
- padding: 5px 10px;
- height: 31px;
-
- font-weight: 500;
- font-size: 14px;
-
- .api-response {
- float: right;
- }
- }
-
- .section-table {
- margin-top: 10px;
-
- .table-row {
- background-color: $color-primary-0;
- border-top: 3px solid $color-primary-3;
- border-bottom: 3px solid $color-primary-3;
- }
-
- .table-cell-action {
- font-size: 13px;
- font-weight: 600;
- }
- }
-
- .section-content {
- background-color: $color-primary-0;
- padding: 10px;
- }
-
- th {
- background-color: $color-primary-2;
- border-top: 3px solid $color-primary-3;
- border-bottom: 3px solid $color-primary-3;
- font-size: 13.5px;
- position: sticky;
- top: 10px;
- padding: 5px;
+ .table-cell-action {
+ font-size: 13px;
+ font-weight: 600;
}
.upload-actions {
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}}
-
-
+
+ {}
+
+
+
);
}
diff --git a/frontend/src/components/panels/RulePane.scss b/frontend/src/components/panels/RulePane.scss
index b030c6a..d45c366 100644
--- a/frontend/src/components/panels/RulePane.scss
+++ b/frontend/src/components/panels/RulePane.scss
@@ -1,16 +1,32 @@
.rule-pane {
- .post-rules-actions {
- display: flex;
+ display: flex;
+ flex-direction: column;
- .rules-options {
- flex: 1;
+ .rules-list {
+ flex: 2 1;
+ overflow: hidden;
+
+ .section-content {
+ height: 100%;
}
- button {
- margin-left: 10px;
+ .section-table {
+ height: calc(100% - 30px);
}
}
+ .rule-edit {
+ flex: 3 0;
+ display: flex;
+ flex-direction: column;
-}
\ No newline at end of file
+ .section-content {
+ flex: 1;
+ }
+
+ .section-table {
+ max-height: 150px;
+ }
+ }
+}
diff --git a/frontend/src/components/panels/common.scss b/frontend/src/components/panels/common.scss
new file mode 100644
index 0000000..cab0de1
--- /dev/null
+++ b/frontend/src/components/panels/common.scss
@@ -0,0 +1,85 @@
+@import '../../colors.scss';
+
+.pane-container {
+ background-color: $color-primary-3;
+ padding: 10px 10px 0;
+ height: 100%;
+
+ .pane-section {
+ margin-bottom: 10px;
+ background-color: $color-primary-0;
+
+ .section-header {
+ background-color: $color-primary-2;
+ padding: 5px 10px;
+ font-weight: 500;
+ font-size: 0.9em;
+ display: flex;
+
+ .api-request {
+ flex: 1;
+ }
+ }
+
+ .section-content {
+ padding: 10px;
+ }
+
+ .section-table {
+ position: relative;
+ overflow-y: scroll;
+
+ .table-error {
+ font-size: 0.8em;
+ color: $color-secondary-0;
+ margin-left: 10px;
+ }
+ }
+
+ table {
+ margin-bottom: 0;
+
+ tbody tr {
+ background-color: $color-primary-3;
+ border-top: 3px solid $color-primary-0;
+ border-bottom: 3px solid $color-primary-0;
+ }
+
+ th {
+ background-color: $color-primary-2;
+ font-size: 0.8em;
+ position: sticky;
+ top: 0;
+ padding: 2px 5px;
+ border: none;
+ }
+
+ .row-small {
+ font-size: 0.9em;
+ }
+
+ .row-clickable {
+ cursor: pointer;
+
+ &:hover {
+ background-color: $color-primary-0;
+ }
+ }
+
+ .row-selected {
+ background-color: $color-primary-0;
+ }
+ }
+ }
+
+ .section-footer {
+ display: flex;
+ padding: 10px;
+ background-color: $color-primary-0;
+ justify-content: flex-end;
+
+ .button-field {
+ margin-left: 10px;
+ }
+ }
+}
diff --git a/frontend/src/validation.js b/frontend/src/validation.js
new file mode 100644
index 0000000..eac8774
--- /dev/null
+++ b/frontend/src/validation.js
@@ -0,0 +1,8 @@
+
+const validation = {
+ isValidColor: (color) => true, // TODO
+ isValidPort: (port, required) => parseInt(port) > (required ? 0 : -1) && parseInt(port) <= 65565,
+ isValidAddress: (address) => true // TODO
+};
+
+export default validation;
diff --git a/frontend/src/views/App.js b/frontend/src/views/App.js
index ccfdb3a..8bd5e46 100644
--- a/frontend/src/views/App.js
+++ b/frontend/src/views/App.js
@@ -5,7 +5,6 @@ import Footer from "./Footer";
import {BrowserRouter as Router} from "react-router-dom";
import Services from "./Services";
import Filters from "./Filters";
-import Rules from "./Rules";
import Config from "./Config";
class App extends Component {
@@ -15,7 +14,6 @@ class App extends Component {
this.state = {
servicesWindowOpen: false,
filterWindowOpen: false,
- rulesWindowOpen: false,
configWindowOpen: false,
configDone: false
};
@@ -40,9 +38,6 @@ class App extends Component {
if (this.state.filterWindowOpen) {
modal =
this.setState({filterWindowOpen: false})}/>;
}
- if (this.state.rulesWindowOpen) {
- modal = this.setState({rulesWindowOpen: false})}/>;
- }
if (this.state.configWindowOpen) {
modal = this.setState({configWindowOpen: false})}
onDone={() => this.setState({configDone: true})}/>;
@@ -53,7 +48,6 @@ class App extends Component {
this.setState({servicesWindowOpen: true})}
onOpenFilters={() => this.setState({filterWindowOpen: true})}
- onOpenRules={() => this.setState({rulesWindowOpen: true})}
onOpenConfig={() => this.setState({configWindowOpen: true})}
onOpenUpload={() => this.setState({uploadWindowOpen: true})}
onConfigDone={this.state.configDone}
diff --git a/frontend/src/views/Config.js b/frontend/src/views/Config.js
index a770378..b11f827 100644
--- a/frontend/src/views/Config.js
+++ b/frontend/src/views/Config.js
@@ -68,8 +68,6 @@ class Config extends Component {
})
};
- let msg = "";
-
fetch('/setup', requestOptions)
.then(response => {
if (response.status === 202 ){
diff --git a/frontend/src/views/Connections.js b/frontend/src/views/Connections.js
index f3fec64..64068c5 100644
--- a/frontend/src/views/Connections.js
+++ b/frontend/src/views/Connections.js
@@ -75,7 +75,7 @@ class Connections extends Component {
}
this.setState({loading: true, prevParams: params});
- let res = await backend.getJson(`${url}?${urlParams}`);
+ let res = (await backend.getJson(`${url}?${urlParams}`)).json;
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.getJson("/api/rules");
+ rules = (await backend.getJson("/api/rules")).json;
flagRule = rules.filter(rule => {
return rule.name === "flag";
})[0];
diff --git a/frontend/src/views/Header.js b/frontend/src/views/Header.js
index 0b82011..06cb20e 100644
--- a/frontend/src/views/Header.js
+++ b/frontend/src/views/Header.js
@@ -1,7 +1,6 @@
import React, {Component} from 'react';
import Typed from 'typed.js';
import './Header.scss';
-import {Button} from "react-bootstrap";
import {filtersDefinitions, filtersNames} from "../components/filters/FiltersDefinitions";
import {Link} from "react-router-dom";
import ButtonField from "../components/fields/ButtonField";
diff --git a/frontend/src/views/MainPane.js b/frontend/src/views/MainPane.js
index 9d3f7b7..6f4e3cd 100644
--- a/frontend/src/views/MainPane.js
+++ b/frontend/src/views/MainPane.js
@@ -22,7 +22,7 @@ class MainPane extends Component {
if (match != null) {
this.setState({loading: true});
backend.getJson(`/api/connections/${match[1]}`)
- .then(connection => this.setState({selectedConnection: connection, loading: false}))
+ .then(res => this.setState({selectedConnection: res.json, loading: false}))
.catch(error => console.log(error));
}
}
diff --git a/frontend/src/views/Rules.js b/frontend/src/views/Rules.js
deleted file mode 100644
index bbc3bb6..0000000
--- a/frontend/src/views/Rules.js
+++ /dev/null
@@ -1,118 +0,0 @@
-import React, {Component} from 'react';
-import './Services.scss';
-import {Button, ButtonGroup, Col, Container, Form, FormControl, InputGroup, Modal, Row, Table} from "react-bootstrap";
-import backend from "../backend";
-
-class Rules extends Component {
-
- constructor(props) {
- super(props);
-
- this.state = {
- rules: []
- };
- }
-
- componentDidMount() {
- this.loadRules();
- }
-
- loadRules() {
- backend.get("/api/rules").then(res => this.setState({rules: res.data}));
- }
-
- render() {
- let rulesRows = this.state.rules.map(rule =>
-
- |
- {rule.name} |
-
- );
-
-
- return (
-
-
-
- ~/rules
-
-
-
-
-
-
-
-
-
- |
- name |
-
-
-
- {rulesRows}
-
-
-
-
-
-
- port:
-
-
-
-
- name:
-
-
-
-
- color:
-
-
-
-
-
-
-
-
-
- notes:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
- }
-}
-
-export default Rules;
--
cgit v1.2.3-70-g09d2
From d5ed31be3b6c97f92be4e94b70d32d1b89932ae9 Mon Sep 17 00:00:00 2001
From: Emiliano Ciavatta
Date: Wed, 30 Sep 2020 15:58:16 +0200
Subject: Complete services page
---
frontend/src/components/fields/InputField.js | 3 +-
.../src/components/fields/extensions/ColorField.js | 34 +++-
.../components/fields/extensions/ColorField.scss | 42 +++--
frontend/src/components/panels/RulePane.js | 16 +-
frontend/src/components/panels/ServicePane.js | 190 +++++++++++++++++++++
frontend/src/components/panels/ServicePane.scss | 22 +++
frontend/src/utils.js | 4 +-
frontend/src/validation.js | 2 +-
frontend/src/views/Header.js | 4 +-
frontend/src/views/MainPane.js | 2 +
10 files changed, 281 insertions(+), 38 deletions(-)
create mode 100644 frontend/src/components/panels/ServicePane.js
create mode 100644 frontend/src/components/panels/ServicePane.scss
(limited to 'frontend/src/components/fields')
diff --git a/frontend/src/components/fields/InputField.js b/frontend/src/components/fields/InputField.js
index b790891..84c981b 100644
--- a/frontend/src/components/fields/InputField.js
+++ b/frontend/src/components/fields/InputField.js
@@ -55,7 +55,8 @@ class InputField extends Component {
{ type === "file" && }
+ aria-describedby={this.id} onChange={handler} {...inputProps}
+ readOnly={this.props.readonly} />
{ type !== "file" && value !== "" &&
diff --git a/frontend/src/components/fields/extensions/ColorField.js b/frontend/src/components/fields/extensions/ColorField.js
index c1c210f..96ebc49 100644
--- a/frontend/src/components/fields/extensions/ColorField.js
+++ b/frontend/src/components/fields/extensions/ColorField.js
@@ -2,6 +2,7 @@ import React, {Component} from 'react';
import {OverlayTrigger, Popover} from "react-bootstrap";
import './ColorField.scss';
import InputField from "../InputField";
+import validation from "../../../validation";
class ColorField extends Component {
@@ -15,11 +16,24 @@ class ColorField extends Component {
"#00897B", "#43A047", "#7CB342", "#9E9D24", "#F9A825", "#FB8C00", "#F4511E", "#6D4C41"];
}
+ componentDidUpdate(prevProps, prevState, snapshot) {
+ if (prevProps.value !== this.props.value) {
+ this.onChange(this.props.value);
+ }
+ }
+
+ onChange = (value) => {
+ this.setState({invalid: value !== "" && !validation.isValidColor(value)});
+
+ if (typeof this.props.onChange === "function") {
+ this.props.onChange(value);
+ }
+ };
+
render() {
const colorButtons = this.colors.map((color) =>
{
- this.setState({color: color});
if (typeof this.props.onChange === "function") {
this.props.onChange(color);
}
@@ -43,18 +57,22 @@ class ColorField extends Component {
);
let buttonStyles = {};
- if (this.state.color) {
- buttonStyles["backgroundColor"] = this.state.color;
+ if (this.props.value) {
+ buttonStyles["backgroundColor"] = this.props.value;
}
return (
-
-
-
-
-
+
+ {this.props.error &&
{this.props.error}
}
);
}
diff --git a/frontend/src/components/fields/extensions/ColorField.scss b/frontend/src/components/fields/extensions/ColorField.scss
index 1c3931f..6eabbda 100644
--- a/frontend/src/components/fields/extensions/ColorField.scss
+++ b/frontend/src/components/fields/extensions/ColorField.scss
@@ -1,29 +1,37 @@
@import '../../../colors.scss';
.color-field {
- display: flex;
- align-items: flex-end;
+ .color-input {
+ display: flex;
+ align-items: flex-end;
- .input-field {
- flex: 1;
+ .input-field {
+ flex: 1;
- input {
- border-bottom-right-radius: 0 !important;
- border-top-right-radius: 0 !important;
+ input {
+ border-bottom-right-radius: 0 !important;
+ border-top-right-radius: 0 !important;
+ }
}
- }
- .color-picker {
- margin-bottom: 5.5px;
+ .color-picker {
+ margin-bottom: 5px;
- .picker-button {
- font-size: 0.8em;
- padding: 8px 15px;
- border-bottom-right-radius: 5px;
- border-top-right-radius: 5px;
- background-color: $color-primary-1;
+ .picker-button {
+ font-size: 0.8em;
+ padding: 8px 15px;
+ border-bottom-right-radius: 5px;
+ border-top-right-radius: 5px;
+ background-color: $color-primary-1;
+ }
}
}
+
+ .color-error {
+ font-size: 0.8em;
+ color: $color-secondary-0;
+ margin-left: 10px;
+ }
}
.colors-container {
@@ -35,4 +43,4 @@
height: 31px;
cursor: pointer;
}
-}
\ No newline at end of file
+}
diff --git a/frontend/src/components/panels/RulePane.js b/frontend/src/components/panels/RulePane.js
index d4c5460..7cb849c 100644
--- a/frontend/src/components/panels/RulePane.js
+++ b/frontend/src/components/panels/RulePane.js
@@ -344,27 +344,29 @@ class RulePane extends Component {
filters:
this.updateParam((r) => r.filter.service_port = v)}
- min={0} max={65565} error={this.state.ruleServicePortError} />
+ 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} />
+ 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)} />
diff --git a/frontend/src/components/panels/ServicePane.js b/frontend/src/components/panels/ServicePane.js
new file mode 100644
index 0000000..b21ad6c
--- /dev/null
+++ b/frontend/src/components/panels/ServicePane.js
@@ -0,0 +1,190 @@
+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";
+
+const classNames = require('classnames');
+const _ = require('lodash');
+
+class ServicePane extends Component {
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ services: [],
+ currentService: this.emptyService,
+ };
+ }
+
+ componentDidMount() {
+ this.reset();
+ this.loadServices();
+ }
+
+ emptyService = {
+ "port": 0,
+ "name": "",
+ "color": "",
+ "notes": ""
+ };
+
+ 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
new file mode 100644
index 0000000..0b154e6
--- /dev/null
+++ b/frontend/src/components/panels/ServicePane.scss
@@ -0,0 +1,22 @@
+
+.service-pane {
+ display: flex;
+ flex-direction: column;
+
+ .services-list {
+ flex: 1;
+ overflow: hidden;
+
+ .section-content {
+ height: 100%;
+ }
+
+ .section-table {
+ height: calc(100% - 30px);
+ }
+ }
+
+ .service-edit {
+ flex: 0;
+ }
+}
diff --git a/frontend/src/utils.js b/frontend/src/utils.js
index 0707575..e71067a 100644
--- a/frontend/src/utils.js
+++ b/frontend/src/utils.js
@@ -1,5 +1,3 @@
-import React from "react";
-
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) {
@@ -111,5 +109,5 @@ export function formatSize(size) {
}
export function randomClassName() {
- return Math.random().toString(36).slice(2)
+ return Math.random().toString(36).slice(2);
}
diff --git a/frontend/src/validation.js b/frontend/src/validation.js
index eac8774..8f3409f 100644
--- a/frontend/src/validation.js
+++ b/frontend/src/validation.js
@@ -1,6 +1,6 @@
const validation = {
- isValidColor: (color) => true, // TODO
+ isValidColor: (color) => /^#(?:[0-9a-fA-F]{3}){1,2}$/.test(color),
isValidPort: (port, required) => parseInt(port) > (required ? 0 : -1) && parseInt(port) <= 65565,
isValidAddress: (address) => true // TODO
};
diff --git a/frontend/src/views/Header.js b/frontend/src/views/Header.js
index 06cb20e..c38c22d 100644
--- a/frontend/src/views/Header.js
+++ b/frontend/src/views/Header.js
@@ -75,7 +75,9 @@ class Header extends Component {
-
+
+
+
diff --git a/frontend/src/views/MainPane.js b/frontend/src/views/MainPane.js
index b9ebadb..d2950ab 100644
--- a/frontend/src/views/MainPane.js
+++ b/frontend/src/views/MainPane.js
@@ -6,6 +6,7 @@ import {Route, Switch, withRouter} from "react-router-dom";
import PcapPane from "../components/panels/PcapPane";
import backend from "../backend";
import RulePane from "../components/panels/RulePane";
+import ServicePane from "../components/panels/ServicePane";
class MainPane extends Component {
@@ -43,6 +44,7 @@ class MainPane extends Component {
} />
} />
+ } />
} />
} />
--
cgit v1.2.3-70-g09d2