diff options
author | Emiliano Ciavatta | 2020-09-23 18:19:09 +0000 |
---|---|---|
committer | Emiliano Ciavatta | 2020-09-23 18:19:09 +0000 |
commit | 8d07bfe5f17534b7301a064aeaf8ed8071f10a40 (patch) | |
tree | f645b9f033d878d54175705c1d1909e0420c54f4 /frontend/src/components | |
parent | 8c8487b79300342b4c6dafe9c3691d43b8dc8e37 (diff) |
Frontend refactor: checkpoint
Diffstat (limited to 'frontend/src/components')
-rw-r--r-- | frontend/src/components/Connection.js | 27 | ||||
-rw-r--r-- | frontend/src/components/ConnectionContent.js | 6 | ||||
-rw-r--r-- | frontend/src/components/fields/StringField.js | 31 | ||||
-rw-r--r-- | frontend/src/components/fields/StringField.scss | 66 | ||||
-rw-r--r-- | frontend/src/components/filters/FiltersDefinitions.js | 33 | ||||
-rw-r--r-- | frontend/src/components/filters/RulesConnectionsFilter.js | 6 | ||||
-rw-r--r-- | frontend/src/components/panels/PcapPane.js | 121 | ||||
-rw-r--r-- | frontend/src/components/panels/PcapPane.scss | 55 |
8 files changed, 313 insertions, 32 deletions
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 { <span className="test-tooltip">{duration}</span> </OverlayTrigger> </td> - <td className="clickable" onClick={this.props.onSelected}>{conn.client_bytes}</td> - <td className="clickable" onClick={this.props.onSelected}>{conn.server_bytes}</td> + <td className="clickable" onClick={this.props.onSelected}>{formatSize(conn.client_bytes)}</td> + <td className="clickable" onClick={this.props.onSelected}>{formatSize(conn.server_bytes)}</td> <td className="contains-flag"> {/*<OverlayTrigger trigger={["focus", "hover"]} placement="right"*/} {/* overlay={popoverFor("hide", <span>Hide this connection from the list</span>)}>*/} 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 ( + <div className={classNames("field", "d-inline-block", {"field-active" : this.props.isActive}, + {"field-invalid": this.props.isInvalid})}> + <div className="input-group"> + <div className="field-name-wrapper"> + <span className="field-name" id={`field-${this.props.name}`}>{this.props.name}:</span> + </div> + <input placeholder={this.props.defaultValue} aria-label={this.props.name} + aria-describedby={`filter-${this.props.name}`} className="field-value" + onChange={this.props.onValueChanged} value={this.props.value} /> + </div> + + { this.props.active && + <div className="field-clear"> + <span className="filter-delete-icon" onClick={() => this.props.onValueChanged("")}>del</span> + </div> + } + </div> + ); + } +} + +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: <StringConnectionsFilter filterName="service_port" defaultFilterValue="all_ports" replaceFunc={cleanNumber} - validateFunc={validatePort}/>, + validateFunc={validatePort} + key="service_port_filter" />, matched_rules: <RulesConnectionsFilter />, client_address: <StringConnectionsFilter filterName="client_address" defaultFilterValue="all_addresses" - validateFunc={validateIpAddress} />, + validateFunc={validateIpAddress} + key="client_address_filter" />, client_port: <StringConnectionsFilter filterName="client_port" defaultFilterValue="all_ports" replaceFunc={cleanNumber} - validateFunc={validatePort}/>, + validateFunc={validatePort} + key="client_port_filter" />, min_duration: <StringConnectionsFilter filterName="min_duration" defaultFilterValue="0" replaceFunc={cleanNumber} - validateFunc={validateMin(0)}/>, + validateFunc={validateMin(0)} + key="min_duration_filter" />, max_duration: <StringConnectionsFilter filterName="max_duration" defaultFilterValue="∞" - replaceFunc={cleanNumber} />, + replaceFunc={cleanNumber} + key="max_duration_filter" />, min_bytes: <StringConnectionsFilter filterName="min_bytes" defaultFilterValue="0" replaceFunc={cleanNumber} - validateFunc={validateMin(0)} />, + validateFunc={validateMin(0)} + key="min_bytes_filter" />, max_bytes: <StringConnectionsFilter filterName="max_bytes" defaultFilterValue="∞" - replaceFunc={cleanNumber} />, + replaceFunc={cleanNumber} + key="max_bytes_filter" />, started_after: <StringConnectionsFilter filterName="started_after" defaultFilterValue="00:00:00" validateFunc={validate24HourTime} encodeFunc={timeToTimestamp} - decodeFunc={timestampToTime} />, + decodeFunc={timestampToTime} + key="started_after_filter" />, started_before: <StringConnectionsFilter filterName="started_before" defaultFilterValue="00:00:00" validateFunc={validate24HourTime} encodeFunc={timeToTimestamp} - decodeFunc={timestampToTime} />, + decodeFunc={timestampToTime} + key="started_before_filter" />, closed_after: <StringConnectionsFilter filterName="closed_after" defaultFilterValue="00:00:00" validateFunc={validate24HourTime} encodeFunc={timeToTimestamp} - decodeFunc={timestampToTime} />, + decodeFunc={timestampToTime} + key="closed_after_filter" />, closed_before: <StringConnectionsFilter filterName="closed_before" defaultFilterValue="00:00:00" validateFunc={validate24HourTime} encodeFunc={timeToTimestamp} - decodeFunc={timestampToTime} />, + decodeFunc={timestampToTime} + key="closed_before_filter" />, marked: <BooleanConnectionsFilter filterName={"marked"} />, hidden: <BooleanConnectionsFilter filterName={"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 => + <tr className="table-row"> + <td>{s["id"].substring(0, 8)}</td> + <td>{timestampToTime2(s["started_at"])}</td> + <td>{((new Date(s["completed_at"]) - new Date(s["started_at"])) / 1000).toFixed(3)}s</td> + <td>{formatSize(s["size"])}</td> + <td>{s["processed_packets"]}</td> + <td>{s["invalid_packets"]}</td> + <td>undefined</td> + <td className="table-cell-action"><a target="_blank" href={"/api/pcap/sessions/" + s["id"] + "/download"}>download</a></td> + </tr> + ); + + return ( + <div className="pane-container"> + <div className="pane-section"> + <div className="section-header"> + <span className="api-request">GET /api/pcap/sessions</span> + <span className="api-response">200 OK</span> + </div> + + <div className="section-table"> + <Table borderless size="sm"> + <thead> + <tr> + <th>id</th> + <th>started_at</th> + <th>duration</th> + <th>size</th> + <th>processed_packets</th> + <th>invalid_packets</th> + <th>packets_per_service</th> + <th>actions</th> + </tr> + </thead> + <tbody> + {sessions} + </tbody> + </Table> + </div> + </div> + + <div className="pane-section"> + <Container className="p-0"> + <Row> + <Col> + <div className="section-header"> + <span className="api-request">POST /api/pcap/upload</span> + <span className="api-response"></span> + </div> + + <div className="section-content"> + <Form.File className="custom-file" onChange={this.onFileChange} + label=".pcap/.pcapng" id="custom-file" + custom={true} + /> + + + <br/><br/><br/><br/> + <StringField /> + + </div> + </Col> + + <Col> + <div className="section-header"> + <span className="api-request">POST /api/pcap/file</span> + <span className="api-response"></span> + </div> + + <div className="section-content"> + <Form.Control type="text" id="pcap-upload" className="custom-file" + onChange={this.onLocalFileChange} placeholder="local .pcap/.pcapng" + custom + /> + </div> + </Col> + </Row> + </Container> + + + + + </div> + + </div> + + ); + } +} + +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 |