From 042cd78894b7c9ce622715e86cb52e203dbe4b34 Mon Sep 17 00:00:00 2001 From: therealbobo Date: Sun, 13 Sep 2020 00:51:55 +0200 Subject: added config menu --- frontend/src/views/Header.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'frontend/src/views/Header.js') diff --git a/frontend/src/views/Header.js b/frontend/src/views/Header.js index 5d0f690..03e4e5c 100644 --- a/frontend/src/views/Header.js +++ b/frontend/src/views/Header.js @@ -72,9 +72,8 @@ class Header extends Component { filters pcaps rules - - services - + services + config -- cgit v1.2.3-70-g09d2 From e1ce4fefede7f956e4b7ae9bc602b4f49fbfad69 Mon Sep 17 00:00:00 2001 From: therealbobo Date: Mon, 14 Sep 2020 11:32:57 +0200 Subject: auto disabling config button --- frontend/src/views/App.js | 9 +++++++-- frontend/src/views/Config.js | 4 ++++ frontend/src/views/Config.scss | 13 +++++++++++++ frontend/src/views/Header.js | 3 ++- 4 files changed, 26 insertions(+), 3 deletions(-) (limited to 'frontend/src/views/Header.js') diff --git a/frontend/src/views/App.js b/frontend/src/views/App.js index 5b49045..5a2d913 100644 --- a/frontend/src/views/App.js +++ b/frontend/src/views/App.js @@ -16,13 +16,16 @@ class App extends Component { servicesWindowOpen: false, filterWindowOpen: false, rulesWindowOpen: false, - configWindowOpen: false + configWindowOpen: false, + configDone: false }; fetch('/api/services') .then(response => { if( response.status === 503){ this.setState({configWindowOpen: true}); + } else if (response.status === 200){ + this.setState({configDone: true}); } }); @@ -41,7 +44,8 @@ class App extends Component { modal = this.setState({rulesWindowOpen: false})}/>; } if (this.state.configWindowOpen) { - modal = this.setState({configWindowOpen: false})}/>; + modal = this.setState({configWindowOpen: false})} + onDone={() => this.setState({configDone: true})}/>; } return ( @@ -51,6 +55,7 @@ class App extends Component { onOpenFilters={() => this.setState({filterWindowOpen: true})} onOpenRules={() => this.setState({rulesWindowOpen: true})} onOpenConfig={() => this.setState({configWindowOpen: true})} + onConfigDone={this.state.configDone} /> }/> diff --git a/frontend/src/views/Config.js b/frontend/src/views/Config.js index 9b220d9..1468b1b 100644 --- a/frontend/src/views/Config.js +++ b/frontend/src/views/Config.js @@ -77,6 +77,8 @@ class Config extends Component { if (response.status === 202 ){ //this.setState({showConfig:false}); this.props.onHide(); + this.props.onDone(); + console.log(this.props.disabled); } } ); @@ -158,6 +160,8 @@ class Config extends Component { + Warning: once the configuration is completed, it cannot be changed unless you reset caronte :( + diff --git a/frontend/src/views/Config.scss b/frontend/src/views/Config.scss index 9946ce9..b0b899f 100644 --- a/frontend/src/views/Config.scss +++ b/frontend/src/views/Config.scss @@ -36,3 +36,16 @@ width: 80px; } } + +.blink{ + + span{ + animation: blink 1s linear infinite; + } + @keyframes blink{ + 0%{opacity: 0;} + 50%{opacity: .5;} + 100%{opacity: 1;} + } + +} diff --git a/frontend/src/views/Header.js b/frontend/src/views/Header.js index 03e4e5c..3f95bcd 100644 --- a/frontend/src/views/Header.js +++ b/frontend/src/views/Header.js @@ -73,7 +73,8 @@ class Header extends Component { pcaps rules services - config + config -- cgit v1.2.3-70-g09d2 From 81e22267dfddca85e9e515ce7683efa9c8541ac8 Mon Sep 17 00:00:00 2001 From: therealbobo Date: Mon, 14 Sep 2020 18:49:09 +0200 Subject: initial upload support --- frontend/src/views/App.js | 6 +++ frontend/src/views/Header.js | 2 +- frontend/src/views/Upload.js | 106 +++++++++++++++++++++++++++++++++++++++++ frontend/src/views/Upload.scss | 21 ++++++++ 4 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 frontend/src/views/Upload.js create mode 100644 frontend/src/views/Upload.scss (limited to 'frontend/src/views/Header.js') diff --git a/frontend/src/views/App.js b/frontend/src/views/App.js index 5a2d913..ebead2f 100644 --- a/frontend/src/views/App.js +++ b/frontend/src/views/App.js @@ -7,6 +7,7 @@ import Services from "./Services"; import Filters from "./Filters"; import Rules from "./Rules"; import Config from "./Config"; +import Upload from "./Upload"; class App extends Component { @@ -17,6 +18,7 @@ class App extends Component { filterWindowOpen: false, rulesWindowOpen: false, configWindowOpen: false, + uploadWindowOpen: false, configDone: false }; @@ -47,6 +49,9 @@ class App extends Component { modal = this.setState({configWindowOpen: false})} onDone={() => this.setState({configDone: true})}/>; } + if (this.state.uploadWindowOpen) { + modal = this.setState({uploadWindowOpen: false}) }/>; + } return ( @@ -55,6 +60,7 @@ class App extends Component { 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/Header.js b/frontend/src/views/Header.js index 3f95bcd..0af7abf 100644 --- a/frontend/src/views/Header.js +++ b/frontend/src/views/Header.js @@ -70,7 +70,7 @@ class Header extends Component { filters - pcaps + pcaps rules services { + this.setState({ selectedFile: event.target.files[0] }); + + }; + + componentDidMount() { + bsCustomFileInput.init() + } + + 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/upload + + + + + + + + + + + + {this.state.errors + .split('\n').map((item, key) => { + return {item}}) + } + + + + + + + + upload + close + + + ); + } +} + +export default Upload; diff --git a/frontend/src/views/Upload.scss b/frontend/src/views/Upload.scss new file mode 100644 index 0000000..e327b8c --- /dev/null +++ b/frontend/src/views/Upload.scss @@ -0,0 +1,21 @@ +@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 8d07bfe5f17534b7301a064aeaf8ed8071f10a40 Mon Sep 17 00:00:00 2001 From: Emiliano Ciavatta Date: Wed, 23 Sep 2020 20:19:09 +0200 Subject: Frontend refactor: checkpoint --- .travis.yml | 1 - frontend/src/backend.js | 36 ++++++ frontend/src/colors.scss | 32 +++++- frontend/src/components/Connection.js | 27 ++--- frontend/src/components/ConnectionContent.js | 6 +- frontend/src/components/fields/StringField.js | 31 ++++++ frontend/src/components/fields/StringField.scss | 66 +++++++++++ .../src/components/filters/FiltersDefinitions.js | 33 ++++-- .../components/filters/RulesConnectionsFilter.js | 6 +- frontend/src/components/panels/PcapPane.js | 121 +++++++++++++++++++++ frontend/src/components/panels/PcapPane.scss | 55 ++++++++++ frontend/src/index.scss | 58 +++++++++- frontend/src/utils.js | 18 +++ frontend/src/views/Config.js | 8 +- frontend/src/views/Connections.js | 23 ++-- frontend/src/views/Footer.js | 4 +- frontend/src/views/Header.js | 12 +- frontend/src/views/Header.scss | 4 + frontend/src/views/MainPane.js | 8 +- frontend/src/views/Rules.js | 4 +- frontend/src/views/Services.js | 33 ++---- 21 files changed, 489 insertions(+), 97 deletions(-) create mode 100644 frontend/src/backend.js create mode 100644 frontend/src/components/fields/StringField.js create mode 100644 frontend/src/components/fields/StringField.scss create mode 100644 frontend/src/components/panels/PcapPane.js create mode 100644 frontend/src/components/panels/PcapPane.scss (limited to 'frontend/src/views/Header.js') diff --git a/.travis.yml b/.travis.yml index 1d66f6e..b59b12f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,6 @@ services: - docker before_script: - - docker pull eciavatta/caronte-env - docker pull mongo:4 - docker-compose -f docker-compose.testing.yml up -d --build diff --git a/frontend/src/backend.js b/frontend/src/backend.js new file mode 100644 index 0000000..35ae6e3 --- /dev/null +++ b/frontend/src/backend.js @@ -0,0 +1,36 @@ + +async function request(method, url, data) { + const options = { + method: method, + mode: "cors", + cache: "no-cache", + credentials: "same-origin", + headers: { + "Content-Type": "application/json" + }, + redirect: "follow", + referrerPolicy: "no-referrer", + }; + if (data != null) { + options.body = JSON.stringify(data); + } + const result = await fetch(url, options); + return result.json(); +} + +const backend = { + get: (url = "") => { + return request("GET", url, null); + }, + post: (url = "", data = null) => { + return request("POST", url, data); + }, + put: (url = "", data = null) => { + return request("PUT", url, data); + }, + delete: (url = "", data = null) => { + return request("DELETE", url, data); + } +}; + +export default backend; diff --git a/frontend/src/colors.scss b/frontend/src/colors.scss index 064f9f5..d982f03 100644 --- a/frontend/src/colors.scss +++ b/frontend/src/colors.scss @@ -10,10 +10,30 @@ $color-secondary-2: #df3030; $color-secondary-3: #ff9d9d; $color-secondary-4: #ffdfdf; -$color-blue: #247085; -$color-blue-light: #a5b8be; -$color-blue-dark: #013b4c; +$color-red: #E53935; +$color-red-light: #FFEBEE; +$color-red-dark: #B71C1C; -$color-green: #25965d; -$color-green-light: #cde4d8; -$color-green-dark: #004321; +$color-pink: #D81B60; +$color-pink-light: #FCE4EC; +$color-pink-dark: #880E4F; + +$color-purple: #8E24AA; +$color-purple-light: #F3E5F5; +$color-purple-dark: #4A148C; + +$color-deep-purple: #5E35B1; +$color-deep-purple-light: #EDE7F6; +$color-deep-purple-dark: #311B92; + +$color-indigo: #3949AB; +$color-indigo-light: #E8EAF6; +$color-indigo-dark: #1A237E; + +$color-green: #43A047; +$color-green-light: #E8F5E9; +$color-green-dark: #1B5E20; + +$color-blue: #1E88E5; +$color-blue-light: #E3F2FD; +$color-blue-dark: #0D47A1; diff --git a/frontend/src/components/Connection.js b/frontend/src/components/Connection.js index 93c6438..1149584 100644 --- a/frontend/src/components/Connection.js +++ b/frontend/src/components/Connection.js @@ -1,7 +1,8 @@ import React, {Component} from 'react'; import './Connection.scss'; -import axios from 'axios' import {Button, Form, OverlayTrigger, Popover} from "react-bootstrap"; +import backend from "../backend"; +import {formatSize} from "../utils"; class Connection extends Component { @@ -19,22 +20,18 @@ class Connection extends Component { handleAction(name) { if (name === "hide") { const enabled = !this.props.data.hidden; - axios.post(`/api/connections/${this.props.data.id}/${enabled ? "hide" : "show"}`) - .then(res => { - if (res.status === 202) { - this.props.onEnabled(!enabled); - this.setState({update: true}); - } + backend.post(`/api/connections/${this.props.data.id}/${enabled ? "hide" : "show"}`) + .then(_ => { + this.props.onEnabled(!enabled); + this.setState({update: true}); }); } if (name === "mark") { const marked = this.props.data.marked; - axios.post(`/api/connections/${this.props.data.id}/${marked ? "unmark" : "mark"}`) - .then(res => { - if (res.status === 202) { - this.props.onMarked(!marked); - this.setState({update: true}); - } + backend.post(`/api/connections/${this.props.data.id}/${marked ? "unmark" : "mark"}`) + .then(_ => { + this.props.onMarked(!marked); + this.setState({update: true}); }); } if (name === "copy") { @@ -114,8 +111,8 @@ class Connection extends Component { {duration} - {conn.client_bytes} - {conn.server_bytes} + {formatSize(conn.client_bytes)} + {formatSize(conn.server_bytes)} {/*Hide this connection from the list)}>*/} diff --git a/frontend/src/components/ConnectionContent.js b/frontend/src/components/ConnectionContent.js index 20ec92b..0c00e8e 100644 --- a/frontend/src/components/ConnectionContent.js +++ b/frontend/src/components/ConnectionContent.js @@ -1,8 +1,8 @@ import React, {Component} from 'react'; import './ConnectionContent.scss'; import {Button, Dropdown, Row} from 'react-bootstrap'; -import axios from 'axios'; import MessageAction from "./MessageAction"; +import backend from "../backend"; const classNames = require('classnames'); @@ -27,9 +27,9 @@ class ConnectionContent extends Component { this.props.connection !== prevProps.connection || this.state.format !== prevState.format)) { this.setState({loading: true}); // TODO: limit workaround. - axios.get(`/api/streams/${this.props.connection.id}?format=${this.state.format}&limit=999999`).then(res => { + backend.get(`/api/streams/${this.props.connection.id}?format=${this.state.format}&limit=999999`).then(res => { this.setState({ - connectionContent: res.data, + connectionContent: res, loading: false }); }); diff --git a/frontend/src/components/fields/StringField.js b/frontend/src/components/fields/StringField.js new file mode 100644 index 0000000..09fe24d --- /dev/null +++ b/frontend/src/components/fields/StringField.js @@ -0,0 +1,31 @@ +import React, {Component} from 'react'; +import './StringField.scss'; + +const classNames = require('classnames'); + +class StringField extends Component { + + render() { + return ( + + + + {this.props.name}: + + + + + { this.props.active && + + this.props.onValueChanged("")}>del + + } + + ); + } +} + +export default StringField; diff --git a/frontend/src/components/fields/StringField.scss b/frontend/src/components/fields/StringField.scss new file mode 100644 index 0000000..7efac56 --- /dev/null +++ b/frontend/src/components/fields/StringField.scss @@ -0,0 +1,66 @@ +@import '../../colors.scss'; + +.field { + margin: 0 10px; + position: relative; + + .field-name-wrapper { + background-color: $color-primary-2; + padding: 3px 7px; + border-top-left-radius: 5px; + border-bottom-left-radius: 5px; + } + + .field-name { + font-size: 13px; + } + + .field-value { + font-size: 13px; + padding-left: 0; + border-radius: 5px; + + &:focus { + background-color: $color-primary-2; + } + } + + &.field-active { + .field-name-wrapper { + background-color: $color-primary-4; + color: $color-primary-3; + } + + .field-value { + background-color: $color-primary-4; + color: $color-primary-3; + } + } + + &.field-invalid { + .field-name-wrapper { + background-color: $color-secondary-2; + color: $color-primary-4; + } + + .field-value { + background-color: $color-secondary-2; + color: $color-primary-4; + } + } + + .field-delete { + position: absolute; + right: 10px; + top: 10px; + z-index: 10; + font-size: 11px; + letter-spacing: -0.5px; + color: $color-primary-2; + cursor: pointer; + + .field-delete-icon { + font-weight: 800; + } + } +} diff --git a/frontend/src/components/filters/FiltersDefinitions.js b/frontend/src/components/filters/FiltersDefinitions.js index a582d02..d36792e 100644 --- a/frontend/src/components/filters/FiltersDefinitions.js +++ b/frontend/src/components/filters/FiltersDefinitions.js @@ -21,49 +21,60 @@ export const filtersDefinitions = { service_port: , + validateFunc={validatePort} + key="service_port_filter" />, matched_rules: , client_address: , + validateFunc={validateIpAddress} + key="client_address_filter" />, client_port: , + validateFunc={validatePort} + key="client_port_filter" />, min_duration: , + validateFunc={validateMin(0)} + key="min_duration_filter" />, max_duration: , + replaceFunc={cleanNumber} + key="max_duration_filter" />, min_bytes: , + validateFunc={validateMin(0)} + key="min_bytes_filter" />, max_bytes: , + replaceFunc={cleanNumber} + key="max_bytes_filter" />, started_after: , + decodeFunc={timestampToTime} + key="started_after_filter" />, started_before: , + decodeFunc={timestampToTime} + key="started_before_filter" />, closed_after: , + decodeFunc={timestampToTime} + key="closed_after_filter" />, closed_before: , + decodeFunc={timestampToTime} + key="closed_before_filter" />, marked: , hidden: }; diff --git a/frontend/src/components/filters/RulesConnectionsFilter.js b/frontend/src/components/filters/RulesConnectionsFilter.js index 358085f..621b6d6 100644 --- a/frontend/src/components/filters/RulesConnectionsFilter.js +++ b/frontend/src/components/filters/RulesConnectionsFilter.js @@ -3,7 +3,7 @@ import {withRouter} from "react-router-dom"; import {Redirect} from "react-router"; import './RulesConnectionsFilter.scss'; import ReactTags from 'react-tag-autocomplete'; -import axios from 'axios'; +import backend from "../../backend"; const classNames = require('classnames'); @@ -24,8 +24,8 @@ class RulesConnectionsFilter extends Component { let params = new URLSearchParams(this.props.location.search); let activeRules = params.getAll("matched_rules") || []; - axios.get("/api/rules").then(res => { - let rules = res.data.flatMap(rule => rule.enabled ? [{id: rule.id, name: rule.name}] : []); + backend.get("/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 new file mode 100644 index 0000000..817c7b5 --- /dev/null +++ b/frontend/src/components/panels/PcapPane.js @@ -0,0 +1,121 @@ +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"; + +class PcapPane extends Component { + + constructor(props) { + super(props); + + this.state = { + sessions: [], + }; + + this.loadSessions = this.loadSessions.bind(this); + } + + componentDidMount() { + this.loadSessions(); + } + + loadSessions() { + backend.get("/api/pcap/sessions").then(res => this.setState({sessions: res})); + } + + render() { + let sessions = this.state.sessions.map(s => + + {s["id"].substring(0, 8)} + {timestampToTime2(s["started_at"])} + {((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/pcap/sessions + 200 OK + + + + + + + id + started_at + duration + size + processed_packets + invalid_packets + packets_per_service + actions + + + + {sessions} + + + + + + + + + + + POST /api/pcap/upload + + + + + + + + + + + + + + + + POST /api/pcap/file + + + + + + + + + + + + + + + + + + ); + } +} + +export default PcapPane; diff --git a/frontend/src/components/panels/PcapPane.scss b/frontend/src/components/panels/PcapPane.scss new file mode 100644 index 0000000..3df87f5 --- /dev/null +++ b/frontend/src/components/panels/PcapPane.scss @@ -0,0 +1,55 @@ +@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; + } + + + +} \ No newline at end of file diff --git a/frontend/src/index.scss b/frontend/src/index.scss index 53ce4dd..358fd70 100644 --- a/frontend/src/index.scss +++ b/frontend/src/index.scss @@ -49,14 +49,62 @@ pre { } .btn-red { - color: $color-secondary-4; - background-color: $color-secondary-2; - border-bottom: 5px solid $color-secondary-1; + color: $color-red-light; + background-color: $color-red; + border-bottom: 5px solid $color-red-dark; &:hover, &:active { - color: $color-secondary-4; - background-color: $color-secondary-1; + 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; } } diff --git a/frontend/src/utils.js b/frontend/src/utils.js index 7381f69..4991755 100644 --- a/frontend/src/utils.js +++ b/frontend/src/utils.js @@ -64,3 +64,21 @@ export function timestampToDateTime(timestamp) { let d = new Date(timestamp); return d.toLocaleDateString() + " " + d.toLocaleTimeString(); } + +export function timestampToTime2(timestamp) { + let d = new Date(timestamp); + let hours = d.getHours(); + let minutes = "0" + d.getMinutes(); + let seconds = "0" + d.getSeconds(); + return hours + ':' + minutes.substr(-2) + ':' + seconds.substr(-2); +} + +export function formatSize(size) { + if (size < 1000) { + return `${size}`; + } else if (size < 1000000) { + return `${(size / 1000).toFixed(1)}K`; + } else { + return `${(size / 1000000).toFixed(1)}M`; + } +} diff --git a/frontend/src/views/Config.js b/frontend/src/views/Config.js index f5766eb..a770378 100644 --- a/frontend/src/views/Config.js +++ b/frontend/src/views/Config.js @@ -1,10 +1,6 @@ -import { - validateIpAddress, -} from "../utils"; -import React, {Component, useState} from 'react'; +import React, {Component} from 'react'; import './Config.scss'; -import {Button, ButtonGroup, ToggleButton, Col, Container, Form, FormControl, InputGroup, Modal, Row, Table} from "react-bootstrap"; -import {createCurlCommand} from '../utils'; +import {Button, ButtonGroup, Col, Container, Form, Modal, Row, Table, ToggleButton} from "react-bootstrap"; class Config extends Component { diff --git a/frontend/src/views/Connections.js b/frontend/src/views/Connections.js index 62733d7..da8958b 100644 --- a/frontend/src/views/Connections.js +++ b/frontend/src/views/Connections.js @@ -1,10 +1,10 @@ import React, {Component} from 'react'; import './Connections.scss'; -import axios from 'axios'; import Connection from "../components/Connection"; import Table from 'react-bootstrap/Table'; import {Redirect} from 'react-router'; import {withRouter} from "react-router-dom"; +import backend from "../backend"; class Connections extends Component { @@ -75,15 +75,15 @@ class Connections extends Component { } this.setState({loading: true, prevParams: params}); - let res = await axios.get(`${url}?${urlParams}`); + let res = await backend.get(`${url}?${urlParams}`); let connections = this.state.connections; let firstConnection = this.state.firstConnection; let lastConnection = this.state.lastConnection; if (params !== undefined && params.from !== undefined) { - if (res.data.length > 0) { - connections = this.state.connections.concat(res.data); + if (res.length > 0) { + connections = this.state.connections.concat(res); lastConnection = connections[connections.length - 1]; if (connections.length > this.maxConnections) { connections = connections.slice(connections.length - this.maxConnections, @@ -92,8 +92,8 @@ class Connections extends Component { } } } else if (params !== undefined && params.to !== undefined) { - if (res.data.length > 0) { - connections = res.data.concat(this.state.connections); + if (res.length > 0) { + connections = res.concat(this.state.connections); firstConnection = connections[0]; if (connections.length > this.maxConnections) { connections = connections.slice(0, this.maxConnections); @@ -101,8 +101,8 @@ class Connections extends Component { } } } else { - if (res.data.length > 0) { - connections = res.data; + if (res.length > 0) { + connections = res; firstConnection = connections[0]; lastConnection = connections[connections.length - 1]; } else { @@ -115,8 +115,7 @@ class Connections extends Component { let flagRule = this.state.flagRule; let rules = this.state.rules; if (flagRule === null) { - let res = await axios.get("/api/rules"); - rules = res.data; + rules = await backend.get("/api/rules"); flagRule = rules.filter(rule => { return rule.name === "flag"; })[0]; @@ -125,7 +124,7 @@ class Connections extends Component { this.setState({ loading: false, connections: connections, - rules: res.data, + rules: res, flagRule: flagRule, firstConnection: firstConnection, lastConnection: lastConnection @@ -134,7 +133,7 @@ class Connections extends Component { render() { let redirect; - let queryString = this.state.queryString !== null ? this.state.queryString : "" + let queryString = this.state.queryString !== null ? this.state.queryString : ""; if (this.state.selected) { let format = this.props.match.params.format; format = format !== undefined ? "/" + format : ""; diff --git a/frontend/src/views/Footer.js b/frontend/src/views/Footer.js index b6ffd9d..0a3c5a3 100644 --- a/frontend/src/views/Footer.js +++ b/frontend/src/views/Footer.js @@ -8,11 +8,11 @@ class Footer extends Component { - ) + ); } } diff --git a/frontend/src/views/Header.js b/frontend/src/views/Header.js index 0af7abf..5860d80 100644 --- a/frontend/src/views/Header.js +++ b/frontend/src/views/Header.js @@ -69,12 +69,12 @@ class Header extends Component { - filters - pcaps - rules - services - config + filters + pcaps + rules + services + config diff --git a/frontend/src/views/Header.scss b/frontend/src/views/Header.scss index e84e758..e36b2d6 100644 --- a/frontend/src/views/Header.scss +++ b/frontend/src/views/Header.scss @@ -16,6 +16,10 @@ .header-buttons { margin: 5px 0; text-align: right; + + button { + margin-left: 10px; + } } .filters-bar-wrapper { diff --git a/frontend/src/views/MainPane.js b/frontend/src/views/MainPane.js index 69de725..3c0d795 100644 --- a/frontend/src/views/MainPane.js +++ b/frontend/src/views/MainPane.js @@ -3,7 +3,8 @@ import './MainPane.scss'; import Connections from "./Connections"; import ConnectionContent from "../components/ConnectionContent"; import {withRouter} from "react-router-dom"; -import axios from 'axios'; +import PcapPane from "../components/panels/PcapPane"; +import backend from "../backend"; class MainPane extends Component { @@ -17,9 +18,9 @@ class MainPane extends Component { componentDidMount() { if ('id' in this.props.match.params) { const id = this.props.match.params.id; - axios.get(`/api/connections/${id}`).then(res => { + backend.get(`/api/connections/${id}`).then(res => { if (res.status === 200) { - this.setState({selectedConnection: res.data}); + this.setState({selectedConnection: res}); } }); } @@ -34,6 +35,7 @@ class MainPane extends Component { this.setState({selectedConnection: c})} /> + {/**/} diff --git a/frontend/src/views/Rules.js b/frontend/src/views/Rules.js index 3424410..bbc3bb6 100644 --- a/frontend/src/views/Rules.js +++ b/frontend/src/views/Rules.js @@ -1,7 +1,7 @@ import React, {Component} from 'react'; import './Services.scss'; import {Button, ButtonGroup, Col, Container, Form, FormControl, InputGroup, Modal, Row, Table} from "react-bootstrap"; -import axios from "axios"; +import backend from "../backend"; class Rules extends Component { @@ -18,7 +18,7 @@ class Rules extends Component { } loadRules() { - axios.get("/api/rules").then(res => this.setState({rules: res.data})); + backend.get("/api/rules").then(res => this.setState({rules: res.data})); } render() { diff --git a/frontend/src/views/Services.js b/frontend/src/views/Services.js index 0de021f..22d61b3 100644 --- a/frontend/src/views/Services.js +++ b/frontend/src/views/Services.js @@ -1,8 +1,8 @@ import React, {Component} from 'react'; import './Services.scss'; import {Button, ButtonGroup, Col, Container, Form, FormControl, InputGroup, Modal, Row, Table} from "react-bootstrap"; -import axios from 'axios' import {createCurlCommand} from '../utils'; +import backend from "../backend"; class Services extends Component { @@ -64,31 +64,20 @@ class Services extends Component { saveService() { if (this.state.portValid && this.state.nameValid) { - const requestOptions = { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - color: this.state.color, - name: this.state.name, - notes: this.state.notes, - port: this.state.port, - }) - }; - - - fetch('/api/services', requestOptions) - .then(function(response){ - console.log(response); - } - ); - - this.newService(); - this.loadServices(); + backend.put("/api/services", { + color: this.state.color, + name: this.state.name, + notes: this.state.notes, + port: this.state.port, + }).then(_ => { + this.newService(); + this.loadServices(); + }); } } loadServices() { - axios.get("/api/services").then(res => this.setState({services: res.data})); + backend.get("/api/services").then(res => this.setState({services: res})); } componentDidUpdate(prevProps, prevState, snapshot) { -- cgit v1.2.3-70-g09d2 From 05678b74d98247c957faa1ca3d0bafc5f68974d1 Mon Sep 17 00:00:00 2001 From: Emiliano Ciavatta Date: Wed, 23 Sep 2020 23:16:58 +0200 Subject: Add BooleanField --- frontend/src/components/fields/BooleanField.js | 37 +++++++++++ frontend/src/components/fields/BooleanField.scss | 34 ++++++++++ frontend/src/components/fields/StringField.js | 16 +++-- frontend/src/components/fields/StringField.scss | 2 +- .../components/filters/BooleanConnectionsFilter.js | 12 ++-- .../filters/BooleanConnectionsFilter.scss | 24 ------- .../src/components/filters/FiltersDefinitions.js | 34 ++++++---- .../components/filters/StringConnectionsFilter.js | 31 ++------- .../filters/StringConnectionsFilter.scss | 66 ------------------- frontend/src/components/panels/PcapPane.js | 4 +- frontend/src/index.scss | 27 +------- frontend/src/utils.js | 4 +- frontend/src/views/Header.js | 6 +- frontend/src/views/Header.scss | 8 ++- frontend/src/views/MainPane.js | 4 +- frontend/yarn.lock | 74 +++++++++++----------- 16 files changed, 168 insertions(+), 215 deletions(-) create mode 100644 frontend/src/components/fields/BooleanField.js create mode 100644 frontend/src/components/fields/BooleanField.scss delete mode 100644 frontend/src/components/filters/BooleanConnectionsFilter.scss delete mode 100644 frontend/src/components/filters/StringConnectionsFilter.scss (limited to 'frontend/src/views/Header.js') diff --git a/frontend/src/components/fields/BooleanField.js b/frontend/src/components/fields/BooleanField.js new file mode 100644 index 0000000..06a6da7 --- /dev/null +++ b/frontend/src/components/fields/BooleanField.js @@ -0,0 +1,37 @@ +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 ( + + + + {(checked ? "✓ " : "✗ ") + name} + + + ); + } +} + +export default BooleanField; diff --git a/frontend/src/components/fields/BooleanField.scss b/frontend/src/components/fields/BooleanField.scss new file mode 100644 index 0000000..6ec25f7 --- /dev/null +++ b/frontend/src/components/fields/BooleanField.scss @@ -0,0 +1,34 @@ +@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/StringField.js b/frontend/src/components/fields/StringField.js index aa23fe8..7781b2d 100644 --- a/frontend/src/components/fields/StringField.js +++ b/frontend/src/components/fields/StringField.js @@ -6,8 +6,14 @@ const classNames = require('classnames'); class StringField extends Component { + constructor(props) { + super(props); + + this.id = `field-${this.props.name || "noname"}-${randomClassName()}`; + } + render() { - const id = `field-${this.props.name || "noname"}-${randomClassName()}`; + const active = this.props.active || false; const invalid = this.props.invalid || false; const small = this.props.small || false; @@ -27,18 +33,18 @@ class StringField extends Component { }; return ( - + { name && - {name}: + {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/views/Header.js') 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 ( - - - - {(checked ? "✓ " : "✗ ") + name} - - - ); - } -} - -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 ( + + + + {(checked ? "✓ " : "✗ ") + name} + + + ); + } +} + +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 && + + {name}: + + } + + + { type === "file" && + {value.name || defaultValue} } + + + { 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 && - - {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 && {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})}/> + + upload + + + @@ -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 { filters - pcaps + + pcaps + rules services this.setState({selectedConnection: c})} /> - - {/**/} + + } /> + } /> + 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 - - - - - - - - - - process_local - - - - upload - - - - - - - - {this.state.errors - .split('\n').map((item, key) => { - return {item}}) - } - - - - - - - - close - - - ); - } -} - -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/views/Header.js') 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 { - - - + - {this.state.copyButtonText} - close + + ); 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 ( + + {this.props.name} + + ); + } +} + +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 ( - + - {(checked ? "✓ " : "✗ ") + name} + {(checked ? "✓ " : "✗ ") + (name != null ? name : "")} ); 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 && {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 && {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 ( + + + + + + pick + + + + + ); + } + +} + +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})}/> + + process + + + - - - - ); } } 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} /> + + + + + + + + + + + + + + + + + options: + + + + + + + + + patterns: + + + + + regex + Aa + .* + \n+ + UTF8 + Uni_ + min + max + direction + actions + + + + + + + + + + + + + s", "s->c"]} value="both" /> + add + + + + + + + + + + + + + + + + ); + } + +} + +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 { pcaps - rules + + rules + services config 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/views/Header.js') 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)}>{serviceName} + 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]) => - { + { this.setState({ messageActionDialog: this.setState({messageActionDialog: null})}/> }); - }}>{actionName} + }} /> ); } @@ -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 && {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 ( - + - - pick + pick - ); } 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})}/> - upload + @@ -192,7 +193,7 @@ class PcapPane extends Component { this.setState({uploadFlushAll: v})}/> - process + 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" /> - add + 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 { - close + ); 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 { - filters + - pcaps + - rules + - services - config + + 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/views/Header.js') 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" && - {value.name || defaultValue} } - } + { 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)} /> - - options: - - - - - - - - - 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 => - - edit - {rule.name} - - ); - - - return ( - - - - ~/rules - - - - - - - - - - new - name - - - - {rulesRows} - - - - - - - - port: - - - - - name: - - - - - color: - - - - - - - - - - notes: - - - - - - - - - - - - - - - - - - - - - - - close - - - ); - } -} - -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/views/Header.js') 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" && {value.name || this.props.placeholder} } + 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 ( - - - - pick - + + + + + pick + + + {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 From d6e2aaad41f916c2080c59cf7b4e42bf87a1a03f Mon Sep 17 00:00:00 2001 From: Emiliano Ciavatta Date: Wed, 30 Sep 2020 22:57:25 +0200 Subject: Complete setup page --- application_router.go | 9 +- frontend/src/components/Connection.js | 18 +- frontend/src/components/Connection.scss | 4 +- frontend/src/components/ConnectionContent.js | 2 +- frontend/src/components/ConnectionMatchedRules.js | 29 +++ .../src/components/ConnectionMatchedRules.scss | 23 ++ .../src/components/panels/ConfigurationPane.js | 162 ++++++++++++++ .../src/components/panels/ConfigurationPane.scss | 19 ++ frontend/src/components/panels/MainPane.js | 56 +++++ frontend/src/components/panels/MainPane.scss | 23 ++ frontend/src/components/panels/PcapPane.js | 5 +- frontend/src/components/panels/RulePane.js | 9 +- frontend/src/components/panels/ServicePane.js | 2 +- frontend/src/index.scss | 7 +- frontend/src/views/App.js | 58 ++--- frontend/src/views/App.scss | 15 ++ frontend/src/views/Config.js | 242 --------------------- frontend/src/views/Config.scss | 55 ----- frontend/src/views/Connections.js | 42 ++-- frontend/src/views/Filters.js | 1 - frontend/src/views/Header.js | 7 +- frontend/src/views/MainPane.js | 59 ----- frontend/src/views/MainPane.scss | 6 - frontend/src/views/Services.js | 210 ------------------ frontend/src/views/Services.scss | 32 --- frontend/yarn.lock | 184 +++++++--------- rules_manager.go | 2 +- 27 files changed, 489 insertions(+), 792 deletions(-) create mode 100644 frontend/src/components/ConnectionMatchedRules.js create mode 100644 frontend/src/components/ConnectionMatchedRules.scss create mode 100644 frontend/src/components/panels/ConfigurationPane.js create mode 100644 frontend/src/components/panels/ConfigurationPane.scss create mode 100644 frontend/src/components/panels/MainPane.js create mode 100644 frontend/src/components/panels/MainPane.scss create mode 100644 frontend/src/views/App.scss delete mode 100644 frontend/src/views/Config.js delete mode 100644 frontend/src/views/Config.scss delete mode 100644 frontend/src/views/MainPane.js delete mode 100644 frontend/src/views/MainPane.scss delete mode 100644 frontend/src/views/Services.js delete mode 100644 frontend/src/views/Services.scss (limited to 'frontend/src/views/Header.js') diff --git a/application_router.go b/application_router.go index 852e68b..501956b 100644 --- a/application_router.go +++ b/application_router.go @@ -20,9 +20,12 @@ func CreateApplicationRouter(applicationContext *ApplicationContext) *gin.Engine router.MaxMultipartMemory = 8 << 30 router.Use(static.Serve("/", static.LocalFile("./frontend/build", true))) - router.GET("/connections/:id", func(c *gin.Context) { - c.File("./frontend/build/index.html") - }) + + for _, path := range []string{"/connections/:id", "/pcaps", "/rules", "/services", "/config"} { + router.GET(path, func(c *gin.Context) { + c.File("./frontend/build/index.html") + }) + } router.POST("/setup", func(c *gin.Context) { if applicationContext.IsConfigured { diff --git a/frontend/src/components/Connection.js b/frontend/src/components/Connection.js index 95b27ff..44f9f18 100644 --- a/frontend/src/components/Connection.js +++ b/frontend/src/components/Connection.js @@ -2,9 +2,11 @@ import React, {Component} from 'react'; import './Connection.scss'; import {Form, OverlayTrigger, Popover} from "react-bootstrap"; import backend from "../backend"; -import {durationBetween, formatSize} from "../utils"; +import {dateTimeToTime, durationBetween, formatSize} from "../utils"; import ButtonField from "./fields/ButtonField"; +const classNames = require('classnames'); + class Connection extends Component { constructor(props) { @@ -60,14 +62,6 @@ class Connection extends Component { Closed at {closedAt.toLocaleDateString() + " " + closedAt.toLocaleTimeString()} ; - let classes = "connection"; - if (this.props.selected) { - classes += " connection-selected"; - } - if (this.props.containsFlag) { - classes += " contains-flag"; - } - const popoverFor = function (name, content) { return @@ -88,7 +82,8 @@ class Connection extends Component { ; return ( - + 0})}> {conn.port_src} {conn.ip_dst} {conn.port_dst} + {dateTimeToTime(conn.started_at)} @@ -107,7 +103,7 @@ class Connection extends Component { {formatSize(conn.client_bytes)} {formatSize(conn.server_bytes)} - + {/*Hide this connection from the list)}>*/} {/* obj == null ? null : Object.entries(obj).map(([key, value]) => - {key}: {value} + {key}: {value} ); let m = connectionMessage.metadata; diff --git a/frontend/src/components/ConnectionMatchedRules.js b/frontend/src/components/ConnectionMatchedRules.js new file mode 100644 index 0000000..21f2a92 --- /dev/null +++ b/frontend/src/components/ConnectionMatchedRules.js @@ -0,0 +1,29 @@ +import React, {Component} from 'react'; +import './ConnectionMatchedRules.scss'; +import ButtonField from "./fields/ButtonField"; + +class ConnectionMatchedRules extends Component { + + constructor(props) { + super(props); + this.state = { + }; + } + + render() { + const matchedRules = this.props.matchedRules.map(mr => { + const rule = this.props.rules.find(r => r.id === mr); + return this.props.addMatchedRulesFilter(rule.id)} name={rule.name} + color={rule.color} small />; + }); + + return ( + + matched_rules: + {matchedRules} + + ); + } +} + +export default ConnectionMatchedRules; diff --git a/frontend/src/components/ConnectionMatchedRules.scss b/frontend/src/components/ConnectionMatchedRules.scss new file mode 100644 index 0000000..ed18f3c --- /dev/null +++ b/frontend/src/components/ConnectionMatchedRules.scss @@ -0,0 +1,23 @@ +@import '../colors.scss'; + +.connection-matches { + background-color: $color-primary-0; + + .rule-buttons { + padding: 0; + } + + .button-field { + display: inline-block; + margin-right: 5px; + + button { + font-size: 0.8em; + padding: 2px 10px; + } + } + + .row-label { + font-size: 0.8em; + } +} diff --git a/frontend/src/components/panels/ConfigurationPane.js b/frontend/src/components/panels/ConfigurationPane.js new file mode 100644 index 0000000..10309f6 --- /dev/null +++ b/frontend/src/components/panels/ConfigurationPane.js @@ -0,0 +1,162 @@ +import React, {Component} from 'react'; +import './common.scss'; +import './ConfigurationPane.scss'; +import LinkPopover from "../objects/LinkPopover"; +import {Col, Container, Row} from "react-bootstrap"; +import InputField from "../fields/InputField"; +import TextField from "../fields/TextField"; +import ButtonField from "../fields/ButtonField"; +import CheckField from "../fields/CheckField"; +import {createCurlCommand} from "../../utils"; +import Table from "react-bootstrap/Table"; +import validation from "../../validation"; +import backend from "../../backend"; + +class ConfigurationPane extends Component { + + constructor(props) { + super(props); + this.state = { + settings: { + "config": { + "server_address": "", + "flag_regex": "", + "auth_required": false + }, + "accounts": { + } + }, + newUsername: "", + newPassword: "" + }; + } + + saveSettings = () => { + if (this.validateSettings(this.state.settings)) { + backend.post("/setup", this.state.settings).then(_ => { + this.props.onConfigured(); + }).catch(res => { + this.setState({setupStatusCode: res.status, setupResponse: JSON.stringify(res.json)}); + }); + } + }; + + validateSettings = (settings) => { + let valid = true; + if (!validation.isValidAddress(settings.config.server_address, true)) { + this.setState({serverAddressError: "invalid ip_address"}); + valid = false; + } + if (settings.config.flag_regex.length < 8) { + this.setState({flagRegexError: "flag_regex.length < 8"}); + valid = false; + } + + return valid; + }; + + updateParam = (callback) => { + callback(this.state.settings); + this.setState({settings: this.state.settings}); + }; + + addAccount = () => { + if (this.state.newUsername.length !== 0 && this.state.newPassword.length !== 0) { + const settings = this.state.settings; + settings.accounts[this.state.newUsername] = this.state.newPassword; + + this.setState({ + newUsername: "", + newPassword: "", + settings: settings + }); + } else { + this.setState({ + newUsernameActive: this.state.newUsername.length === 0, + newPasswordActive: this.state.newPassword.length === 0 + }); + } + }; + + render() { + const settings = this.state.settings; + const curlCommand = createCurlCommand("/setup", "POST", settings); + + const accounts = Object.entries(settings.accounts).map(([username, password]) => + + {username} + + this.updateParam((s) => delete s.accounts[username]) }/> + ).concat( + this.setState({newUsername: v})} /> + this.setState({newPassword: v})} /> + + ); + + return ( + + + + + + POST /setup + + + + + + + + this.updateParam((s) => s.config.server_address = v)} /> + this.updateParam((s) => s.config.flag_regex = v)} + error={this.state.flagRegexError} /> + + this.updateParam((s) => s.config.auth_required = v)}/> + + + + + + accounts: + + + + + username + password + actions + + + + {accounts} + + + + + + + + + + + + + + + + + + ); + } +} + +export default ConfigurationPane; diff --git a/frontend/src/components/panels/ConfigurationPane.scss b/frontend/src/components/panels/ConfigurationPane.scss new file mode 100644 index 0000000..955d2bc --- /dev/null +++ b/frontend/src/components/panels/ConfigurationPane.scss @@ -0,0 +1,19 @@ +@import '../../colors'; + +.configuration-pane { + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background-color: $color-primary-0; + + .pane { + flex-basis: 900px; + margin-bottom: 200px; + } + + .pane-container { + padding-bottom: 1px; + } + +} diff --git a/frontend/src/components/panels/MainPane.js b/frontend/src/components/panels/MainPane.js new file mode 100644 index 0000000..3202d6d --- /dev/null +++ b/frontend/src/components/panels/MainPane.js @@ -0,0 +1,56 @@ +import React, {Component} from 'react'; +import './common.scss'; +import './MainPane.scss'; +import Connections from "../../views/Connections"; +import ConnectionContent from "../ConnectionContent"; +import {Route, Switch, withRouter} from "react-router-dom"; +import PcapPane from "./PcapPane"; +import backend from "../../backend"; +import RulePane from "./RulePane"; +import ServicePane from "./ServicePane"; + +class MainPane extends Component { + + constructor(props) { + super(props); + this.state = { + selectedConnection: null, + loading: false + }; + } + + componentDidMount() { + const match = this.props.location.pathname.match(/^\/connections\/([a-f0-9]{24})$/); + if (match != null) { + this.setState({loading: true}); + backend.get(`/api/connections/${match[1]}`) + .then(res => this.setState({selectedConnection: res.json, loading: false})) + .catch(error => console.log(error)); + } + } + + render() { + return ( + + + { + !this.state.loading && + this.setState({selectedConnection: c})} + initialConnection={this.state.selectedConnection} /> + } + + + + } /> + } /> + } /> + } /> + } /> + + + + ); + } +} + +export default withRouter(MainPane); diff --git a/frontend/src/components/panels/MainPane.scss b/frontend/src/components/panels/MainPane.scss new file mode 100644 index 0000000..04be347 --- /dev/null +++ b/frontend/src/components/panels/MainPane.scss @@ -0,0 +1,23 @@ +@import '../../colors'; + +.main-pane { + height: 100%; + display: flex; + padding: 0 15px; + background-color: $color-primary-2; + + .pane { + flex: 1; + } + + .connections-pane { + flex: 1 0; + margin-right: 7.5px; + } + + .details-pane { + flex: 1 1; + margin-left: 7.5px; + } + +} diff --git a/frontend/src/components/panels/PcapPane.js b/frontend/src/components/panels/PcapPane.js index e83e3da..7b3fde6 100644 --- a/frontend/src/components/panels/PcapPane.js +++ b/frontend/src/components/panels/PcapPane.js @@ -109,7 +109,7 @@ class PcapPane extends Component { render() { let sessions = this.state.sessions.map(s => - + {s["id"].substring(0, 8)} {dateTimeToTime(s["started_at"])} {durationBetween(s["started_at"], s["completed_at"])} @@ -119,8 +119,7 @@ class PcapPane extends Component { - download + download ); diff --git a/frontend/src/components/panels/RulePane.js b/frontend/src/components/panels/RulePane.js index 7cb849c..49364d2 100644 --- a/frontend/src/components/panels/RulePane.js +++ b/frontend/src/components/panels/RulePane.js @@ -13,6 +13,7 @@ import ChoiceField from "../fields/ChoiceField"; import ButtonField from "../fields/ButtonField"; import validation from "../../validation"; import LinkPopover from "../objects/LinkPopover"; +import {randomClassName} from "../../utils"; const classNames = require('classnames'); const _ = require('lodash'); @@ -220,7 +221,7 @@ class RulePane extends Component { 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 })}> @@ -235,7 +236,7 @@ class RulePane extends Component { rule.patterns.concat(this.state.newPattern) : rule.patterns ).map(p => p === pattern ? - + { @@ -272,7 +273,7 @@ class RulePane extends Component { : - + {p.regex} {p.flags.caseless ? "yes": "no"} {p.flags.dot_all ? "yes": "no"} @@ -377,7 +378,7 @@ class RulePane extends Component { regex - Aa + !Aa .* \n+ UTF8 diff --git a/frontend/src/components/panels/ServicePane.js b/frontend/src/components/panels/ServicePane.js index b21ad6c..eaefa64 100644 --- a/frontend/src/components/panels/ServicePane.js +++ b/frontend/src/components/panels/ServicePane.js @@ -100,7 +100,7 @@ class ServicePane extends Component { 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 })}> diff --git a/frontend/src/index.scss b/frontend/src/index.scss index 9dcc692..9ba23de 100644 --- a/frontend/src/index.scss +++ b/frontend/src/index.scss @@ -11,8 +11,7 @@ body { -moz-osx-font-smoothing: grayscale; background-color: $color-primary-2; color: $color-primary-4; - height: 100%; - max-height: 100%; + height: 100vh; font-size: 100%; } @@ -67,10 +66,6 @@ a { } } -textarea.form-control { - resize: none; -} - .table { color: $color-primary-4; } diff --git a/frontend/src/views/App.js b/frontend/src/views/App.js index 8bd5e46..fb4454c 100644 --- a/frontend/src/views/App.js +++ b/frontend/src/views/App.js @@ -1,62 +1,46 @@ import React, {Component} from 'react'; +import './App.scss'; import Header from "./Header"; -import MainPane from "./MainPane"; +import MainPane from "../components/panels/MainPane"; import Footer from "./Footer"; import {BrowserRouter as Router} from "react-router-dom"; -import Services from "./Services"; import Filters from "./Filters"; -import Config from "./Config"; +import backend from "../backend"; +import ConfigurationPane from "../components/panels/ConfigurationPane"; class App extends Component { constructor(props) { super(props); - this.state = { - servicesWindowOpen: false, - filterWindowOpen: false, - configWindowOpen: false, - configDone: false - }; - - fetch('/api/services') - .then(response => { - if( response.status === 503){ - this.setState({configWindowOpen: true}); - } else if (response.status === 200){ - this.setState({configDone: true}); - } - }); + this.state = {}; + } + componentDidMount() { + backend.get("/api/services").then(_ => this.setState({configured: true})); } render() { let modal; - if (this.state.servicesWindowOpen) { - modal = this.setState({servicesWindowOpen: false})}/>; - } - if (this.state.filterWindowOpen) { + if (this.state.filterWindowOpen && this.state.configured) { modal = this.setState({filterWindowOpen: false})}/>; } - if (this.state.configWindowOpen) { - modal = this.setState({configWindowOpen: false})} - onDone={() => this.setState({configDone: true})}/>; - } return ( - + - this.setState({servicesWindowOpen: true})} - onOpenFilters={() => this.setState({filterWindowOpen: true})} - onOpenConfig={() => this.setState({configWindowOpen: true})} - onOpenUpload={() => this.setState({uploadWindowOpen: true})} - onConfigDone={this.state.configDone} - /> - - {modal} - + + this.setState({filterWindowOpen: true})} /> + + + {this.state.configured ? : + this.setState({configured: true})} />} + {modal} + + + {this.state.configured && } + - ); } diff --git a/frontend/src/views/App.scss b/frontend/src/views/App.scss new file mode 100644 index 0000000..b25d4c9 --- /dev/null +++ b/frontend/src/views/App.scss @@ -0,0 +1,15 @@ + +.main { + display: flex; + flex-direction: column; + height: 100vh; + + .main-content { + flex: 1 1; + overflow: hidden; + } + + .main-header, .main-footer { + flex: 0 0; + } +} diff --git a/frontend/src/views/Config.js b/frontend/src/views/Config.js deleted file mode 100644 index b11f827..0000000 --- a/frontend/src/views/Config.js +++ /dev/null @@ -1,242 +0,0 @@ -import React, {Component} from 'react'; -import './Config.scss'; -import {Button, ButtonGroup, Col, Container, Form, Modal, Row, Table, ToggleButton} from "react-bootstrap"; - -class Config extends Component { - - constructor(props) { - super(props); - - this.state = { - server_address: "", - flag_regex: "", - auth_required: false, - accounts: {}, - showSignup: false, - showConfig: true, - tmpUser:"", - tmpPass:"", - tmpConf:"", - errors:"" - }; - - this.serverIpChanged = this.serverIpChanged.bind(this); - this.flagRegexChanged = this.flagRegexChanged.bind(this); - this.authRequiredChanged = this.authRequiredChanged.bind(this); - this.userChanged = this.userChanged.bind(this); - this.passwdChanged = this.passwdChanged.bind(this); - this.confirmChanged = this.confirmChanged.bind(this); - } - - serverIpChanged(event) { - this.setState({server_address: event.target.value}); - } - - flagRegexChanged(event) { - this.setState({flag_regex: event.target.value}); - } - - authRequiredChanged() { - this.setState({auth_required: !this.value}); - this.checked = !this.checked; - this.value = !this.value; - } - - userChanged(event) { - this.setState({tmpUser: event.target.value}); - } - - passwdChanged(event) { - this.setState({tmpPass: event.target.value}); - } - - confirmChanged(event) { - this.setState({tmpConf: event.target.value}); - } - - setup() { - const requestOptions = { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - config: { - server_address: this.state.server_address, - flag_regex: this.state.flag_regex, - auth_required: this.state.auth_required, - }, - accounts: this.state.accounts - }) - }; - - fetch('/setup', requestOptions) - .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()} - ); - }); - } - } - ); - - } - - signup(){ - if (this.state.tmpPass === this.state.tmpConf){ - const accounts = {...this.state.accounts}; - accounts[this.state.tmpUser] = this.state.tmpPass; - this.setState({accounts}); - console.log(this.state); - this.setState({showSignup:false,showConfig:true}) - } - this.setState({tmpUser : ""}); - this.setState({tmpPass : ""}); - this.setState({tmpConf : ""}); - } - - render() { - let rows = Object.keys(this.state.accounts).map(u => - - {u} - - ); - - - - return ( - <> - - - - # passwd - - - - - - - - username: - - - - - password: - - - - - confirm password: - - - - - - - - - - - - this.signup()}>signup - this.setState({showSignup:false,showConfig:true})}>close - - - - - - - ~/.config - - - - Warning: once the configuration is completed, it cannot be changed unless you reset caronte :( - - - - - - - this.authRequiredChanged()} - > - Authentication - - - - - - - - users - - - - {rows} - - this.setState({showSignup:true,showConfig:false})}>new - - - - - - - - - - - - - server_address: - - - - - flag_regex: - - - - - - - - - - - - {this.state.errors - .split('\n').map((item, key) => { - return {item}}) - } - - - - - - - - this.setup()}>set - close - - - > - ); - } -} - -export default Config; diff --git a/frontend/src/views/Config.scss b/frontend/src/views/Config.scss deleted file mode 100644 index 331d7a7..0000000 --- a/frontend/src/views/Config.scss +++ /dev/null @@ -1,55 +0,0 @@ -@import '../colors.scss'; - -.curl-output { - width: 100%; - font-size: 13px; -} - -#passwd-form { - margin:auto; -} - -.users-list { - .btn { - width: 70px; - } - - td { - background-color: $color-primary-2; - border-top: 2px solid $color-primary-0; - vertical-align: middle; - text-align: center; - } - - th { - background-color: $color-primary-1; - text-align: center; - } -} - -.btn-color { - border: 3px solid #fff; -} - -.dialog-footer { - .btn { - width: 80px; - } -} - -.blink{ - - span{ - animation: blink 1s linear infinite; - } - @keyframes blink{ - 0%{opacity: 0;} - 50%{opacity: .5;} - 100%{opacity: 1;} - } - -} - -.error{ - color: red; -} diff --git a/frontend/src/views/Connections.js b/frontend/src/views/Connections.js index 9dca7e9..73979c4 100644 --- a/frontend/src/views/Connections.js +++ b/frontend/src/views/Connections.js @@ -5,6 +5,7 @@ import Table from 'react-bootstrap/Table'; import {Redirect} from 'react-router'; import {withRouter} from "react-router-dom"; import backend from "../backend"; +import ConnectionMatchedRules from "../components/ConnectionMatchedRules"; class Connections extends Component { @@ -62,11 +63,21 @@ class Connections extends Component { }; addServicePortFilter = (port) => { - let urlParams = new URLSearchParams(this.props.location.search); + const urlParams = new URLSearchParams(this.props.location.search); urlParams.set("service_port", port); this.setState({queryString: "?" + urlParams}); }; + addMatchedRulesFilter = (matchedRule) => { + const urlParams = new URLSearchParams(this.props.location.search); + const oldMatchedRules = urlParams.getAll("matched_rules") || []; + + if (!oldMatchedRules.includes(matchedRule)) { + urlParams.append("matched_rules", matchedRule); + this.setState({queryString: "?" + urlParams}); + } + }; + async loadConnections(params) { let url = "/api/connections"; const urlParams = new URLSearchParams(this.props.location.search); @@ -112,20 +123,15 @@ class Connections extends Component { } } - let flagRule = this.state.flagRule; let rules = this.state.rules; - if (flagRule === null) { + if (rules === null) { rules = (await backend.get("/api/rules")).json; - flagRule = rules.filter(rule => { - return rule.name === "flag"; - })[0]; } this.setState({ loading: false, connections: connections, - rules: res, - flagRule: flagRule, + rules: rules, firstConnection: firstConnection, lastConnection: lastConnection }); @@ -158,6 +164,7 @@ class Connections extends Component { srcport dstip dstport + started_at duration up down @@ -166,13 +173,18 @@ class Connections extends Component { { - this.state.connections.map(c => - this.connectionSelected(c)} - selected={this.state.selected === c.id} onMarked={marked => c.marked = marked} - onEnabled={enabled => c.hidden = !enabled} - containsFlag={this.state.flagRule && c.matched_rules.includes(this.state.flagRule.id)} - addServicePortFilter={this.addServicePortFilter}/> - ) + this.state.connections.flatMap(c => { + return [ this.connectionSelected(c)} + selected={this.state.selected === c.id} + onMarked={marked => c.marked = marked} + onEnabled={enabled => c.hidden = !enabled} + addServicePortFilter={this.addServicePortFilter} />, + c.matched_rules.length > 0 && + + ]; + }) } {loading} diff --git a/frontend/src/views/Filters.js b/frontend/src/views/Filters.js index b62e7eb..ba7d467 100644 --- a/frontend/src/views/Filters.js +++ b/frontend/src/views/Filters.js @@ -1,5 +1,4 @@ import React, {Component} from 'react'; -import './Services.scss'; import {Col, Container, Modal, Row, Table} from "react-bootstrap"; import {filtersDefinitions, filtersNames} from "../components/filters/FiltersDefinitions"; import ButtonField from "../components/fields/ButtonField"; diff --git a/frontend/src/views/Header.js b/frontend/src/views/Header.js index c38c22d..944f1d5 100644 --- a/frontend/src/views/Header.js +++ b/frontend/src/views/Header.js @@ -46,7 +46,7 @@ class Header extends Component { render() { let quickFilters = filtersNames.filter(name => this.state[`${name}_active`]) - .map(name => filtersDefinitions[name]) + .map(name => {filtersDefinitions[name]}) .slice(0, 5); return ( @@ -78,8 +78,9 @@ class Header extends Component { - + + + diff --git a/frontend/src/views/MainPane.js b/frontend/src/views/MainPane.js deleted file mode 100644 index d2950ab..0000000 --- a/frontend/src/views/MainPane.js +++ /dev/null @@ -1,59 +0,0 @@ -import React, {Component} from 'react'; -import './MainPane.scss'; -import Connections from "./Connections"; -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"; -import ServicePane from "../components/panels/ServicePane"; - -class MainPane extends Component { - - constructor(props) { - super(props); - this.state = { - selectedConnection: null, - loading: false - }; - } - - componentDidMount() { - const match = this.props.location.pathname.match(/^\/connections\/([a-f0-9]{24})$/); - if (match != null) { - this.setState({loading: true}); - backend.get(`/api/connections/${match[1]}`) - .then(res => this.setState({selectedConnection: res.json, loading: false})) - .catch(error => console.log(error)); - } - } - - render() { - return ( - - - - - { - !this.state.loading && - this.setState({selectedConnection: c})} - initialConnection={this.state.selectedConnection} /> - } - - - - } /> - } /> - } /> - } /> - } /> - - - - - - ); - } -} - -export default withRouter(MainPane); diff --git a/frontend/src/views/MainPane.scss b/frontend/src/views/MainPane.scss deleted file mode 100644 index 20720ba..0000000 --- a/frontend/src/views/MainPane.scss +++ /dev/null @@ -1,6 +0,0 @@ -.main-pane { - .pane { - height: calc(100vh - 210px); - position: relative; - } -} diff --git a/frontend/src/views/Services.js b/frontend/src/views/Services.js deleted file mode 100644 index 97368dc..0000000 --- a/frontend/src/views/Services.js +++ /dev/null @@ -1,210 +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 {createCurlCommand} from '../utils'; -import backend from "../backend"; - -class Services extends Component { - - constructor(props) { - super(props); - this.alphabet = 'abcdefghijklmnopqrstuvwxyz'; - this.colors = ["#E53935", "#D81B60", "#8E24AA", "#5E35B1", "#3949AB", "#1E88E5", "#039BE5", "#00ACC1", - "#00897B", "#43A047", "#7CB342", "#9E9D24", "#F9A825", "#FB8C00", "#F4511E", "#6D4C41"]; - - this.state = { - services: {}, - port: 0, - portValid: false, - name: "", - nameValid: false, - color: this.colors[0], - colorValid: false, - notes: "" - }; - - this.portChanged = this.portChanged.bind(this); - this.nameChanged = this.nameChanged.bind(this); - this.notesChanged = this.notesChanged.bind(this); - this.newService = this.newService.bind(this); - this.editService = this.editService.bind(this); - this.saveService = this.saveService.bind(this); - this.loadServices = this.loadServices.bind(this); - } - - componentDidMount() { - this.loadServices(); - } - - portChanged(event) { - let value = event.target.value.replace(/[^\d]/gi, ''); - let port = 0; - if (value !== "") { - port = parseInt(value); - } - this.setState({port: port}); - } - - nameChanged(event) { - let value = event.target.value.replace(/[\s]/gi, '_').replace(/[^\w]/gi, '').toLowerCase(); - this.setState({name: value}); - } - - notesChanged(event) { - this.setState({notes: event.target.value}); - } - - newService() { - this.setState({name: "", port: 0, notes: ""}); - } - - editService(service) { - this.setState({name: service.name, port: service.port, color: service.color, notes: service.notes}); - } - - saveService() { - if (this.state.portValid && this.state.nameValid) { - backend.put("/api/services", { - color: this.state.color, - name: this.state.name, - notes: this.state.notes, - port: this.state.port, - }).then(_ => { - this.newService(); - this.loadServices(); - }); - } - } - - loadServices() { - backend.get("/api/services").then(res => this.setState({services: res})); - } - - componentDidUpdate(prevProps, prevState, snapshot) { - if (this.state.name != null && prevState.name !== this.state.name) { - this.setState({ - nameValid: this.state.name.length >= 3 - }); - } - if (prevState.port !== this.state.port) { - this.setState({ - portValid: this.state.port > 0 && this.state.port <= 65565 - }); - } - } - - render() { - let output = ""; - if (!this.state.portValid) { - output += "assert(1 <= port <= 65565)\n"; - } - if (!this.state.nameValid) { - output += "assert(len(name) >= 3)\n"; - } - if (output === "") { - output = createCurlCommand("/services", "PUT", { - "port": this.state.port, - "name": this.state.name, - "color": this.state.color, - "notes": this.state.notes - }); - } - let rows = Object.values(this.state.services).map(s => - - this.editService(s)} style={{"backgroundColor": s.color}}>edit - {s.port} - {s.name} - - ); - - let colorButtons = this.colors.map((color, i) => - this.setState({color: color})}>{this.alphabet[i]}); - - return ( - - - - ~/services - - - - - - - - - - new - port - name - - - - {rows} - - - - - - - port: - - - - - name: - - - - - color: - - {colorButtons.slice(0, 8)} - - - {colorButtons.slice(8, 18)} - - - - - notes: - - - - - - - - - - - - - - - - - - - - - save - close - - - ); - } -} - -export default Services; diff --git a/frontend/src/views/Services.scss b/frontend/src/views/Services.scss deleted file mode 100644 index 2abb55e..0000000 --- a/frontend/src/views/Services.scss +++ /dev/null @@ -1,32 +0,0 @@ -@import '../colors.scss'; - -.curl-output { - width: 100%; - font-size: 13px; -} - -.services-list { - .btn { - width: 70px; - } - - td { - background-color: $color-primary-2; - border-top: 2px solid $color-primary-0; - vertical-align: middle; - } - - th { - background-color: $color-primary-1; - } -} - -.btn-color { - border: 3px solid #fff; -} - -.dialog-footer { - .btn { - width: 80px; - } -} diff --git a/frontend/yarn.lock b/frontend/yarn.lock index c5f59cb..6d111a3 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1166,24 +1166,24 @@ resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-10.1.0.tgz#f0950bba18819512d42f7197e56c518aa491cf18" integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg== -"@fortawesome/fontawesome-common-types@^0.2.30": - version "0.2.30" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.30.tgz#2f1cc5b46bd76723be41d0013a8450c9ba92b777" - integrity sha512-TsRwpTuKwFNiPhk1UfKgw7zNPeV5RhNp2Uw3pws+9gDAkPGKrtjR1y2lI3SYn7+YzyfuNknflpBA1LRKjt7hMg== +"@fortawesome/fontawesome-common-types@^0.2.31": + version "0.2.31" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.31.tgz#f15a39e5ab4e5dfda0717733bcacb9580e666ad9" + integrity sha512-xfnPyH6NN5r/h1/qDYoGB0BlHSID902UkQzxR8QsoKDh55KAPr8ruAoie12WQEEQT8lRE2wtV7LoUllJ1HqCag== "@fortawesome/fontawesome-svg-core@^1.2.28": - version "1.2.30" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.30.tgz#f56dc6791861fe5d1af04fb8abddb94658c576db" - integrity sha512-E3sAXATKCSVnT17HYmZjjbcmwihrNOCkoU7dVMlasrcwiJAHxSKeZ+4WN5O+ElgO/FaYgJmASl8p9N7/B/RttA== + version "1.2.31" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.31.tgz#23fff9c521f1b57e79e4c2fd6cce8aa794e9d99e" + integrity sha512-lqUWRK+ylHQJG5Kiez4XrAZAfc7snxCc+X59quL3xPfMnxzfyf1lt+/hD7X1ZL4KlzAH2KFzMuEVrolo/rAkog== dependencies: - "@fortawesome/fontawesome-common-types" "^0.2.30" + "@fortawesome/fontawesome-common-types" "^0.2.31" "@fortawesome/free-solid-svg-icons@^5.13.0": - version "5.14.0" - resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.14.0.tgz#970453f5e8c4915ad57856c3a0252ac63f6fec18" - integrity sha512-M933RDM8cecaKMWDSk3FRYdnzWGW7kBBlGNGfvqLVwcwhUPNj9gcw+xZMrqBdRqxnSXdl3zWzTCNNGEtFUq67Q== + version "5.15.0" + resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.0.tgz#6f553f25cdebd7d5ae778598c2ddd3321dd63450" + integrity sha512-4dGRsOnGBPM7c0fd3LuiU6LgRSLn01rw1LJ370yC2iFMLUcLCLLynZhQbMhsiJmMwQM/YmPQblAdyHKVCgsIAA== dependencies: - "@fortawesome/fontawesome-common-types" "^0.2.30" + "@fortawesome/fontawesome-common-types" "^0.2.31" "@fortawesome/react-fontawesome@^0.1.9": version "0.1.11" @@ -1407,9 +1407,9 @@ integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== "@popperjs/core@^2.0.0": - version "2.4.4" - resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.4.4.tgz#11d5db19bd178936ec89cd84519c4de439574398" - integrity sha512-1oO6+dN5kdIA3sKPZhRGJTfGVP4SWV6KqlMOwry4J3HfyD68sl/3KmG7DeYUzvN+RbhXDnv/D8vNNB8168tAMg== + version "2.5.3" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.5.3.tgz#4982b0b66b7a4cf949b86f5d25a8cf757d3cfd9d" + integrity sha512-RFwCobxsvZ6j7twS7dHIZQZituMIDJJNHS/qY6iuthVebxS3zhRY+jaC2roEKiAYaVuTcGmX6Luc6YBcf6zJVg== "@restart/context@^2.1.4": version "2.1.4" @@ -1533,9 +1533,9 @@ loader-utils "^1.2.3" "@testing-library/dom@*": - version "7.24.2" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.24.2.tgz#6d2b7dd21efbd5358b98c2777fc47c252f3ae55e" - integrity sha512-ERxcZSoHx0EcN4HfshySEWmEf5Kkmgi+J7O79yCJ3xggzVlBJ2w/QjJUC+EBkJJ2OeSw48i3IoePN4w8JlVUIA== + version "7.24.3" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.24.3.tgz#dae3071463cf28dc7755b43d9cf2202e34cbb85d" + integrity sha512-6eW9fUhEbR423FZvoHRwbWm9RUUByLWGayYFNVvqTnQLYvsNpBS4uEuKH9aqr3trhxFwGVneJUonehL3B1sHJw== dependencies: "@babel/code-frame" "^7.10.4" "@babel/runtime" "^7.10.3" @@ -1593,9 +1593,9 @@ integrity sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A== "@types/babel__core@^7.1.0": - version "7.1.9" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.9.tgz#77e59d438522a6fb898fa43dc3455c6e72f3963d" - integrity sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw== + version "7.1.10" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.10.tgz#ca58fc195dd9734e77e57c6f2df565623636ab40" + integrity sha512-x8OM8XzITIMyiwl5Vmo2B1cR1S1Ipkyv4mdlbJjMa1lmuKvKY9FrBbEANIaMlnWn5Rf7uO+rC/VgYabNkE17Hw== dependencies: "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" @@ -1604,24 +1604,24 @@ "@types/babel__traverse" "*" "@types/babel__generator@*": - version "7.6.1" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.1.tgz#4901767b397e8711aeb99df8d396d7ba7b7f0e04" - integrity sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew== + version "7.6.2" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.2.tgz#f3d71178e187858f7c45e30380f8f1b7415a12d8" + integrity sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ== dependencies: "@babel/types" "^7.0.0" "@types/babel__template@*": - version "7.0.2" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.0.2.tgz#4ff63d6b52eddac1de7b975a5223ed32ecea9307" - integrity sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg== + version "7.0.3" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.0.3.tgz#b8aaeba0a45caca7b56a5de9459872dde3727214" + integrity sha512-uCoznIPDmnickEi6D0v11SBpW0OuVqHJCa7syXqQHy5uktSCreIlt0iglsCnmvz8yCb38hGcWeseA8cWJSwv5Q== dependencies: "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.0.14" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.14.tgz#e99da8c075d4fb098c774ba65dabf7dc9954bd13" - integrity sha512-8w9szzKs14ZtBVuP6Wn7nMLRJ0D6dfB0VEBEyRgxrZ/Ln49aNMykrghM2FaNn4FJRzNppCSa0Rv9pBRM5Xc3wg== + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.15.tgz#db9e4238931eb69ef8aab0ad6523d4d4caa39d03" + integrity sha512-Pzh9O3sTK8V6I1olsXpCfj2k/ygO2q1X0vhhnDrEQyYLHZesWz+zMZMVcwXLCYf0U36EtmyYaFGPfXlTtDHe3A== dependencies: "@babel/types" "^7.3.0" @@ -1691,9 +1691,9 @@ integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== "@types/node@*": - version "14.11.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.1.tgz#56af902ad157e763f9ba63d671c39cda3193c835" - integrity sha512-oTQgnd0hblfLsJ6BvJzzSL+Inogp3lq9fGgqRkMB/ziKMgEUaFl801OncOzUmalfzt14N0oPHMK47ipl+wbTIw== + version "14.11.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.2.tgz#2de1ed6670439387da1c9f549a2ade2b0a799256" + integrity sha512-jiE3QIxJ8JLNcb1Ps6rDbysDhN4xa8DJJvuC9prr6w+1tIh+QAbYyNF3tyiZNLDBIuBCf4KEcV2UvQm/V60xfA== "@types/parse-json@^4.0.0": version "4.0.0" @@ -1771,16 +1771,16 @@ integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw== "@types/yargs@^13.0.0": - version "13.0.10" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.10.tgz#e77bf3fc73c781d48c2eb541f87c453e321e5f4b" - integrity sha512-MU10TSgzNABgdzKvQVW1nuuT+sgBMWeXNc3XOs5YXV5SDAK+PPja2eUuBNB9iqElu03xyEDqlnGw0jgl4nbqGQ== + version "13.0.11" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.11.tgz#def2f0c93e4bdf2c61d7e34899b17e34be28d3b1" + integrity sha512-NRqD6T4gktUrDi1o1wLH3EKC1o2caCr7/wR87ODcbVITQF106OM3sFN92ysZ++wqelOd1CTzatnOBRDYYG6wGQ== dependencies: "@types/yargs-parser" "*" "@types/yargs@^15.0.0": - version "15.0.5" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.5.tgz#947e9a6561483bdee9adffc983e91a6902af8b79" - integrity sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w== + version "15.0.7" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.7.tgz#dad50a7a234a35ef9460737a56024287a3de1d2b" + integrity sha512-Gf4u3EjaPNcC9cTu4/j2oN14nSVhr8PQ+BvBcBQHAhDZfl0bVIiLgvnRXv/dn58XhTm9UXvBpvJpDlwV65QxOA== dependencies: "@types/yargs-parser" "*" @@ -2393,13 +2393,6 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428" integrity sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA== -axios@^0.19.2: - version "0.19.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" - integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== - dependencies: - follow-redirects "1.5.10" - axobject-query@^2.0.2: version "2.2.0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" @@ -2792,12 +2785,12 @@ 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.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.14.3.tgz#381f9e7f13794b2eb17e1761b4f118e8ae665a53" - integrity sha512-GcZPC5+YqyPO4SFnz48/B0YaCwS47Q9iPChRGi6t7HhflKBcINzFrJvRfC+jp30sRMKxF+d4EHGs27Z0XP1NaQ== + version "4.14.5" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.14.5.tgz#1c751461a102ddc60e40993639b709be7f2c4015" + integrity sha512-Z+vsCZIvCBvqLoYkBFTwEYH3v5MCQbsAjp50ERycpOjnPmolg1Gjy4+KaWWpm8QOJt9GHkhdqAl14NpCX73CWA== dependencies: - caniuse-lite "^1.0.30001131" - electron-to-chromium "^1.3.570" + caniuse-lite "^1.0.30001135" + electron-to-chromium "^1.3.571" escalade "^3.1.0" node-releases "^1.1.61" @@ -2982,10 +2975,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.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== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001035, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001135: + version "1.0.30001140" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001140.tgz#30dae27599f6ede2603a0962c82e468bca894232" + integrity sha512-xFtvBtfGrpjTOxTpjP5F2LmN04/ZGfYV8EQzUIC/RmKpdrmzJrjqlJ4ho7sGuAMPko2/Jl08h7x9uObCfBFaAA== capture-exit@^2.0.0: version "2.0.0" @@ -3812,13 +3805,6 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: dependencies: ms "2.0.0" -debug@=3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== - dependencies: - ms "2.0.0" - debug@^3.1.1, debug@^3.2.5: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" @@ -4026,9 +4012,9 @@ dom-accessibility-api@^0.3.0: integrity sha512-PzwHEmsRP3IGY4gv/Ug+rMeaTIyTJvadCb+ujYXYeIylbHJezIyNToe8KfEgHTCEYyC+/bUghYOGg8yMGlZ6vA== dom-accessibility-api@^0.5.1: - version "0.5.2" - resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.2.tgz#ef3cdb5d3f0d599d8f9c8b18df2fb63c9793739d" - integrity sha512-k7hRNKAiPJXD2aBqfahSo4/01cTsKWXf+LqJgglnkN2Nz8TsxXKQBXHhKe0Ye9fEfHEZY49uSA5Sr3AqP/sWKA== + version "0.5.3" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.3.tgz#0ea493c924d4070dfbf531c4aaca3d7a2c601aab" + integrity sha512-yfqzAi1GFxK6EoJIZKgxqJyK6j/OjEFEUi2qkNThD/kUhoCFSG1izq31B5xuxzbJBGw9/67uPtkPMYAzWL7L7Q== dom-converter@^0.2: version "0.2.0" @@ -4151,10 +4137,10 @@ 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.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== +electron-to-chromium@^1.3.378, electron-to-chromium@^1.3.571: + version "1.3.576" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.576.tgz#2e70234484e03d7c7e90310d7d79fd3775379c34" + integrity sha512-uSEI0XZ//5ic+0NdOqlxp0liCD44ck20OAGyLMSymIWTEAtHKVJi6JM18acOnRgUgX7Q65QqnI+sNncNvIy8ew== elliptic@^6.5.3: version "6.5.3" @@ -4252,20 +4238,20 @@ es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstrac string.prototype.trimstart "^1.0.1" es-abstract@^1.18.0-next.0: - version "1.18.0-next.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.0.tgz#b302834927e624d8e5837ed48224291f2c66e6fc" - integrity sha512-elZXTZXKn51hUBdJjSZGYRujuzilgXo8vSPQzjGYXLvSlGiCo8VO8ZGV3kjo9a0WNJJ57hENagwbtlRuHuzkcQ== + version "1.18.0-next.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68" + integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA== dependencies: es-to-primitive "^1.2.1" function-bind "^1.1.1" has "^1.0.3" has-symbols "^1.0.1" - is-callable "^1.2.0" + is-callable "^1.2.2" is-negative-zero "^2.0.0" is-regex "^1.1.1" object-inspect "^1.8.0" object-keys "^1.1.1" - object.assign "^4.1.0" + object.assign "^4.1.1" string.prototype.trimend "^1.0.1" string.prototype.trimstart "^1.0.1" @@ -4927,13 +4913,6 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" -follow-redirects@1.5.10: - version "1.5.10" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" - integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== - dependencies: - debug "=3.1.0" - follow-redirects@^1.0.0: version "1.13.0" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" @@ -5858,10 +5837,10 @@ is-buffer@^1.0.2, is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-callable@^1.1.4, is-callable@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.1.tgz#4d1e21a4f437509d25ce55f8184350771421c96d" - integrity sha512-wliAfSzx6V+6WfMOmus1xy0XvSgf/dlStkvTfq7F0g4bOIW0PSUbnyse3NhDwdyYS1ozfUtAAySqTws3z9Eqgg== +is-callable@^1.1.4, is-callable@^1.2.0, is-callable@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" + integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== is-ci@^2.0.0: version "2.0.0" @@ -6991,7 +6970,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.5, lodash@~4.17.10: +"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.5, lodash@~4.17.10: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== @@ -7215,11 +7194,16 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@1.44.0, "mime-db@>= 1.43.0 < 2": +mime-db@1.44.0: version "1.44.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== +"mime-db@>= 1.43.0 < 2": + version "1.45.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea" + integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w== + mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: version "2.1.27" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" @@ -7697,7 +7681,7 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@^4.1.0: +object.assign@^4.1.0, object.assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.1.tgz#303867a666cdd41936ecdedfb1f8f3e32a478cdd" integrity sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA== @@ -7783,9 +7767,9 @@ onetime@^5.1.0: mimic-fn "^2.1.0" open@^7.0.2: - version "7.2.1" - resolved "https://registry.yarnpkg.com/open/-/open-7.2.1.tgz#07b0ade11a43f2a8ce718480bdf3d7563a095195" - integrity sha512-xbYCJib4spUdmcs0g/2mK1nKo/jO2T7INClWd/beL7PFkXRWgr8B23ssDHX/USPn2M2IjDR5UdpYs6I67SnTSA== + version "7.3.0" + resolved "https://registry.yarnpkg.com/open/-/open-7.3.0.tgz#45461fdee46444f3645b6e14eb3ca94b82e1be69" + integrity sha512-mgLwQIx2F/ye9SmbrUkurZCnkoXyXyu9EbHtJZrICjVAJfyMArdHp3KkixGdZx1ZHFPNIwl0DDM1dFFqXbTLZw== dependencies: is-docker "^2.0.0" is-wsl "^2.1.1" @@ -8469,9 +8453,9 @@ postcss-lab-function@^2.0.1: postcss-values-parser "^2.0.0" postcss-load-config@^2.0.0: - 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== + version "2.1.2" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.2.tgz#c5ea504f2c4aef33c7359a34de3573772ad7502a" + integrity sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw== dependencies: cosmiconfig "^5.0.0" import-cwd "^2.0.0" @@ -8845,9 +8829,9 @@ 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.3" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.3.tgz#766d77728728817cc140fa1ac6da5e77f9fada98" - integrity sha512-0ClFaY4X1ra21LRqbW6y3rUbWcxnSVkDFG57R7Nxus9J9myPFlv+jYDMohzpkBx0RrjjiqjtycpchQ+PLGmZ9w== + version "6.0.4" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz#56075a1380a04604c38b063ea7767a129af5c2b3" + integrity sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw== dependencies: cssesc "^3.0.0" indexes-of "^1.0.1" @@ -8902,9 +8886,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.34" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.34.tgz#f2baf57c36010df7de4009940f21532c16d65c20" - integrity sha512-H/7V2VeNScX9KE83GDrDZNiGT1m2H+UTnlinIzhjlLX9hfMUn1mHNnGeX81a1c8JSBdBvqk7c2ZOG6ZPn5itGw== + version "7.0.35" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.35.tgz#d2be00b998f7f211d8a276974079f2e92b970e24" + integrity sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg== dependencies: chalk "^2.4.2" source-map "^0.6.1" diff --git a/rules_manager.go b/rules_manager.go index 36379e6..a5dc7ce 100644 --- a/rules_manager.go +++ b/rules_manager.go @@ -106,7 +106,7 @@ func LoadRulesManager(storage Storage, flagRegex string) (RulesManager, error) { if len(rulesManager.rules) == 0 { if _, err := rulesManager.AddRule(context.Background(), Rule{ Name: "flag", - Color: "#ff0000", + Color: "#E53935", Notes: "Mark connections where the flag is stolen", Patterns: []Pattern{ {Regex: flagRegex, Direction: DirectionToClient}, -- cgit v1.2.3-70-g09d2
{key}: {value}