diff options
Diffstat (limited to 'frontend/src')
-rw-r--r-- | frontend/src/components/Header.js | 47 | ||||
-rw-r--r-- | frontend/src/components/Header.scss | 9 | ||||
-rw-r--r-- | frontend/src/components/dialogs/Filters.js | 108 | ||||
-rw-r--r-- | frontend/src/components/dialogs/Filters.scss | 5 | ||||
-rw-r--r-- | frontend/src/components/fields/ButtonField.js | 3 | ||||
-rw-r--r-- | frontend/src/components/fields/ButtonField.scss | 7 | ||||
-rw-r--r-- | frontend/src/components/filters/AdvancedFilters.js | 54 | ||||
-rw-r--r-- | frontend/src/components/filters/BooleanConnectionsFilter.js | 2 | ||||
-rw-r--r-- | frontend/src/components/filters/FiltersDefinitions.js | 73 | ||||
-rw-r--r-- | frontend/src/components/filters/StringConnectionsFilter.js | 2 | ||||
-rw-r--r-- | frontend/src/components/panels/ConnectionsPane.js | 22 | ||||
-rw-r--r-- | frontend/src/dispatcher.js | 2 | ||||
-rw-r--r-- | frontend/src/index.js | 6 | ||||
-rw-r--r-- | frontend/src/utils.js | 16 |
14 files changed, 158 insertions, 198 deletions
diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js index b4a2177..119958b 100644 --- a/frontend/src/components/Header.js +++ b/frontend/src/components/Header.js @@ -18,21 +18,17 @@ import React, {Component} from 'react'; import Typed from 'typed.js'; import './Header.scss'; -import {filtersDefinitions, filtersNames} from "./filters/FiltersDefinitions"; import {Link, withRouter} from "react-router-dom"; import ButtonField from "./fields/ButtonField"; import ExitSearchFilter from "./filters/ExitSearchFilter"; +import {cleanNumber, validatePort} from "../utils"; +import StringConnectionsFilter from "./filters/StringConnectionsFilter"; +import RulesConnectionsFilter from "./filters/RulesConnectionsFilter"; +import BooleanConnectionsFilter from "./filters/BooleanConnectionsFilter"; +import AdvancedFilters from "./filters/AdvancedFilters"; class Header extends Component { - constructor(props) { - super(props); - let newState = {}; - filtersNames.forEach(elem => newState[`${elem}_active`] = false); - this.state = newState; - this.fetchStateFromLocalStorage = this.fetchStateFromLocalStorage.bind(this); - } - componentDidMount() { const options = { strings: ["caronte$ "], @@ -40,33 +36,13 @@ class Header extends Component { cursorChar: "❚" }; this.typed = new Typed(this.el, options); - - this.fetchStateFromLocalStorage(); - - if (typeof window !== "undefined") { - window.addEventListener("quick-filters", this.fetchStateFromLocalStorage); - } } componentWillUnmount() { this.typed.destroy(); - - if (typeof window) { - window.removeEventListener("quick-filters", this.fetchStateFromLocalStorage); - } - } - - fetchStateFromLocalStorage() { - let newState = {}; - filtersNames.forEach(elem => newState[`${elem}_active`] = localStorage.getItem(`filters.${elem}`) === "true"); - this.setState(newState); } render() { - let quickFilters = filtersNames.filter(name => this.state[`${name}_active`]) - .map(name => <React.Fragment key={name}>{filtersDefinitions[name]}</React.Fragment>) - .slice(0, 5); - return ( <header className="header container-fluid"> <div className="row"> @@ -82,14 +58,21 @@ class Header extends Component { <div className="col-auto"> <div className="filters-bar"> - {quickFilters} - <ExitSearchFilter /> + <StringConnectionsFilter filterName="service_port" + defaultFilterValue="all_ports" + replaceFunc={cleanNumber} + validateFunc={validatePort} + key="service_port_filter" + width={200} small inline/> + <RulesConnectionsFilter/> + <BooleanConnectionsFilter filterName={"marked"}/> + <ExitSearchFilter/> + <AdvancedFilters onClick={this.props.onOpenFilters} /> </div> </div> <div className="col"> <div className="header-buttons"> - <ButtonField variant="pink" onClick={this.props.onOpenFilters} name="filters" bordered/> <Link to={"/searches" + this.props.location.search}> <ButtonField variant="pink" name="searches" bordered/> </Link> diff --git a/frontend/src/components/Header.scss b/frontend/src/components/Header.scss index e2e8e1c..fff28e6 100644 --- a/frontend/src/components/Header.scss +++ b/frontend/src/components/Header.scss @@ -26,9 +26,16 @@ .filters-bar { padding: 3px 0; - .filter { + .filter, + .button-field { display: inline-block; margin-right: 10px; } + + .button-field button { + font-weight: 400; + padding: 7px 10px; + border-radius: 5px; + } } } diff --git a/frontend/src/components/dialogs/Filters.js b/frontend/src/components/dialogs/Filters.js index dfd554b..d2cce4f 100644 --- a/frontend/src/components/dialogs/Filters.js +++ b/frontend/src/components/dialogs/Filters.js @@ -16,91 +16,63 @@ */ import React, {Component} from 'react'; -import {Col, Container, Modal, Row, Table} from "react-bootstrap"; -import {filtersDefinitions, filtersNames} from "../filters/FiltersDefinitions"; +import {Col, Container, Modal, Row} from "react-bootstrap"; import ButtonField from "../fields/ButtonField"; +import './Filters.scss'; +import {cleanNumber, validateIpAddress, validateMin, validatePort} from "../../utils"; +import StringConnectionsFilter from "../filters/StringConnectionsFilter"; class Filters extends Component { - constructor(props) { - super(props); - let newState = {}; - filtersNames.forEach(elem => newState[`${elem}_active`] = false); - this.state = newState; - } - - componentDidMount() { - let newState = {}; - filtersNames.forEach(elem => newState[`${elem}_active`] = localStorage.getItem(`filters.${elem}`) === "true"); - this.setState(newState); - } - - checkboxChangesHandler(filterName, event) { - this.setState({[`${filterName}_active`]: event.target.checked}); - localStorage.setItem(`filters.${filterName}`, event.target.checked); - if (typeof window !== "undefined") { - window.dispatchEvent(new Event("quick-filters")); - } - } - - generateRows(filtersNames) { - return filtersNames.map(name => - <tr key={name}> - <td><input type="checkbox" - checked={this.state[`${name}_active`]} - onChange={event => this.checkboxChangesHandler(name, event)}/></td> - <td>{filtersDefinitions[name]}</td> - </tr> - ); - } - render() { return ( <Modal {...this.props} - show="true" + show={true} size="lg" aria-labelledby="filters-dialog" centered > <Modal.Header> <Modal.Title id="filters-dialog"> - ~/filters + ~/advanced_filters </Modal.Title> </Modal.Header> <Modal.Body> - <Container> - <Row> - <Col md={6}> - <Table borderless size="sm" className="filters-table"> - <thead> - <tr> - <th>show</th> - <th>filter</th> - </tr> - </thead> - <tbody> - {this.generateRows(["service_port", "client_address", "min_duration", - "min_bytes"])} - </tbody> - </Table> - </Col> - <Col md={6}> - <Table borderless size="sm" className="filters-table"> - <thead> - <tr> - <th>show</th> - <th>filter</th> - </tr> - </thead> - <tbody> - {this.generateRows(["matched_rules", "client_port", "max_duration", - "max_bytes", "marked"])} - </tbody> - </Table> - </Col> - </Row> - </Container> + <div className="advanced-filters d-flex"> + <div className="flex-fill"> + <StringConnectionsFilter filterName="client_address" + defaultFilterValue="all_addresses" + validateFunc={validateIpAddress} + key="client_address_filter"/> + <StringConnectionsFilter filterName="min_duration" + defaultFilterValue="0" + replaceFunc={cleanNumber} + validateFunc={validateMin(0)} + key="min_duration_filter"/> + <StringConnectionsFilter filterName="min_bytes" + defaultFilterValue="0" + replaceFunc={cleanNumber} + validateFunc={validateMin(0)} + key="min_bytes_filter"/> + </div> + + <div className="flex-fill"> + <StringConnectionsFilter filterName="client_port" + defaultFilterValue="all_ports" + replaceFunc={cleanNumber} + validateFunc={validatePort} + key="client_port_filter"/> + <StringConnectionsFilter filterName="max_duration" + defaultFilterValue="∞" + replaceFunc={cleanNumber} + key="max_duration_filter"/> + <StringConnectionsFilter filterName="max_bytes" + defaultFilterValue="∞" + replaceFunc={cleanNumber} + key="max_bytes_filter"/> + </div> + </div> </Modal.Body> <Modal.Footer className="dialog-footer"> <ButtonField variant="red" bordered onClick={this.props.onHide} name="close"/> diff --git a/frontend/src/components/dialogs/Filters.scss b/frontend/src/components/dialogs/Filters.scss new file mode 100644 index 0000000..7d09380 --- /dev/null +++ b/frontend/src/components/dialogs/Filters.scss @@ -0,0 +1,5 @@ +.advanced-filters { + .filter { + margin: 10px; + } +} diff --git a/frontend/src/components/fields/ButtonField.js b/frontend/src/components/fields/ButtonField.js index 193339c..850f837 100644 --- a/frontend/src/components/fields/ButtonField.js +++ b/frontend/src/components/fields/ButtonField.js @@ -55,7 +55,8 @@ class ButtonField extends Component { } return ( - <div className={classNames("field", "button-field", {"field-small": this.props.small})}> + <div className={classNames("field", "button-field", {"field-small": this.props.small}, + {"field-active": this.props.active})}> <button type="button" className={classNames(buttonClassnames)} onClick={handler} style={buttonStyle}>{this.props.name}</button> </div> diff --git a/frontend/src/components/fields/ButtonField.scss b/frontend/src/components/fields/ButtonField.scss index 9e46b9f..99afe08 100644 --- a/frontend/src/components/fields/ButtonField.scss +++ b/frontend/src/components/fields/ButtonField.scss @@ -15,6 +15,13 @@ } } + &.field-active { + button { + color: $color-primary-1; + background-color: $color-primary-4; + } + } + .button-variant-red { color: $color-red-light; background-color: $color-red; diff --git a/frontend/src/components/filters/AdvancedFilters.js b/frontend/src/components/filters/AdvancedFilters.js new file mode 100644 index 0000000..f5ba825 --- /dev/null +++ b/frontend/src/components/filters/AdvancedFilters.js @@ -0,0 +1,54 @@ +/* + * This file is part of caronte (https://github.com/eciavatta/caronte). + * Copyright (c) 2020 Emiliano Ciavatta. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +import React, {Component} from 'react'; +import {withRouter} from "react-router-dom"; +import dispatcher from "../../dispatcher"; +import ButtonField from "../fields/ButtonField"; +import {updateParams} from "../../utils"; + +class AdvancedFilters extends Component { + + state = {}; + + componentDidMount() { + this.urlParams = new URLSearchParams(this.props.location.search); + + this.connectionsFiltersCallback = payload => { + this.urlParams = updateParams(this.urlParams, payload); + const active = ["client_address", "client_port", "min_duration", "max_duration", "min_bytes", "max_bytes"] + .some(f => this.urlParams.has(f)); + if (this.state.active !== active) { + this.setState({active: active}); + } + }; + dispatcher.register("connections_filters", this.connectionsFiltersCallback); + } + + componentWillUnmount() { + dispatcher.unregister(this.connectionsFiltersCallback); + } + + render() { + return ( + <ButtonField onClick={this.props.onClick} name="advanced_filters" small active={this.state.active}/> + ); + } + +} + +export default withRouter(AdvancedFilters); diff --git a/frontend/src/components/filters/BooleanConnectionsFilter.js b/frontend/src/components/filters/BooleanConnectionsFilter.js index c611a0d..9558323 100644 --- a/frontend/src/components/filters/BooleanConnectionsFilter.js +++ b/frontend/src/components/filters/BooleanConnectionsFilter.js @@ -59,7 +59,7 @@ class BooleanConnectionsFilter extends Component { return ( <div className="filter" style={{"width": `${this.props.width}px`}}> <CheckField checked={this.toBoolean(this.state.filterActive)} name={this.props.filterName} - onChange={this.filterChanged}/> + onChange={this.filterChanged} small/> </div> ); } diff --git a/frontend/src/components/filters/FiltersDefinitions.js b/frontend/src/components/filters/FiltersDefinitions.js deleted file mode 100644 index 9fb3b18..0000000 --- a/frontend/src/components/filters/FiltersDefinitions.js +++ /dev/null @@ -1,73 +0,0 @@ -/* - * This file is part of caronte (https://github.com/eciavatta/caronte). - * Copyright (c) 2020 Emiliano Ciavatta. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -import {cleanNumber, validateIpAddress, validateMin, validatePort} from "../../utils"; -import StringConnectionsFilter from "./StringConnectionsFilter"; -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", "marked"]; - -export const filtersDefinitions = { - service_port: <StringConnectionsFilter filterName="service_port" - defaultFilterValue="all_ports" - replaceFunc={cleanNumber} - validateFunc={validatePort} - key="service_port_filter" - width={200}/>, - matched_rules: <RulesConnectionsFilter/>, - client_address: <StringConnectionsFilter filterName="client_address" - defaultFilterValue="all_addresses" - validateFunc={validateIpAddress} - key="client_address_filter" - width={320}/>, - client_port: <StringConnectionsFilter filterName="client_port" - defaultFilterValue="all_ports" - replaceFunc={cleanNumber} - validateFunc={validatePort} - key="client_port_filter" - width={200}/>, - min_duration: <StringConnectionsFilter filterName="min_duration" - defaultFilterValue="0" - replaceFunc={cleanNumber} - validateFunc={validateMin(0)} - key="min_duration_filter" - width={200}/>, - max_duration: <StringConnectionsFilter filterName="max_duration" - defaultFilterValue="∞" - replaceFunc={cleanNumber} - key="max_duration_filter" - width={200}/>, - min_bytes: <StringConnectionsFilter filterName="min_bytes" - defaultFilterValue="0" - replaceFunc={cleanNumber} - validateFunc={validateMin(0)} - key="min_bytes_filter" - width={200}/>, - max_bytes: <StringConnectionsFilter filterName="max_bytes" - defaultFilterValue="∞" - replaceFunc={cleanNumber} - key="max_bytes_filter" - width={200}/>, - contains_string: <StringConnectionsFilter filterName="contains_string" - defaultFilterValue="" - key="contains_string_filter" - width={320}/>, - marked: <BooleanConnectionsFilter filterName={"marked"}/> -}; diff --git a/frontend/src/components/filters/StringConnectionsFilter.js b/frontend/src/components/filters/StringConnectionsFilter.js index c833220..18b3784 100644 --- a/frontend/src/components/filters/StringConnectionsFilter.js +++ b/frontend/src/components/filters/StringConnectionsFilter.js @@ -127,7 +127,7 @@ class StringConnectionsFilter extends Component { <div className="filter" style={{"width": `${this.props.width}px`}}> <InputField active={active} invalid={this.state.invalidValue} name={this.props.filterName} placeholder={this.props.defaultFilterValue} onChange={this.filterChanged} - value={this.state.fieldValue} inline={true} small={true}/> + value={this.state.fieldValue} inline={this.props.inline} small={this.props.small}/> </div> ); } diff --git a/frontend/src/components/panels/ConnectionsPane.js b/frontend/src/components/panels/ConnectionsPane.js index 33dd7c1..89859e6 100644 --- a/frontend/src/components/panels/ConnectionsPane.js +++ b/frontend/src/components/panels/ConnectionsPane.js @@ -26,6 +26,7 @@ import log from "../../log"; import ButtonField from "../fields/ButtonField"; import dispatcher from "../../dispatcher"; import {Redirect} from "react-router"; +import {updateParams} from "../../utils"; const classNames = require('classnames'); @@ -67,29 +68,16 @@ class ConnectionsPane extends Component { this.loadConnections(additionalParams, urlParams, true).then(() => log.debug("Connections loaded")); this.connectionsFiltersCallback = payload => { - const params = this.state.urlParams; - const initialParams = params.toString(); - - Object.entries(payload).forEach(([key, value]) => { - if (value == null) { - params.delete(key); - } else if (Array.isArray(value)) { - params.delete(key); - value.forEach(v => params.append(key, v)); - } else { - params.set(key, value); - } - }); - - if (initialParams === params.toString()) { + const newParams = updateParams(this.state.urlParams, payload); + if (this.state.urlParams.toString() === newParams.toString()) { return; } log.debug("Update following url params:", payload); this.queryStringRedirect = true; - this.setState({urlParams}); + this.setState({urlParams: newParams}); - this.loadConnections({limit: this.queryLimit}, urlParams) + this.loadConnections({limit: this.queryLimit}, newParams) .then(() => log.info("ConnectionsPane reloaded after query string update")); }; dispatcher.register("connections_filters", this.connectionsFiltersCallback); diff --git a/frontend/src/dispatcher.js b/frontend/src/dispatcher.js index fa08d48..881970f 100644 --- a/frontend/src/dispatcher.js +++ b/frontend/src/dispatcher.js @@ -47,7 +47,7 @@ class Dispatcher { }; unregister = (callback) => { - this.listeners = _.without(callback); + _.remove(this.listeners, l => l.callback === callback); }; } diff --git a/frontend/src/index.js b/frontend/src/index.js index ca1273a..4bc2730 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -26,9 +26,9 @@ import notifications from "./notifications"; notifications.createWebsocket(); ReactDOM.render( - <React.StrictMode> - <App /> - </React.StrictMode>, + // <React.StrictMode> + <App />, + // </React.StrictMode>, document.getElementById('root') ); diff --git a/frontend/src/utils.js b/frontend/src/utils.js index f333f0d..06414ac 100644 --- a/frontend/src/utils.js +++ b/frontend/src/utils.js @@ -146,3 +146,19 @@ export function downloadBlob(blob, fileName) { a.click(); window.URL.revokeObjectURL(url); } + +export function updateParams(urlParams, payload) { + const params = new URLSearchParams(urlParams.toString()); + Object.entries(payload).forEach(([key, value]) => { + if (value == null) { + params.delete(key); + } else if (Array.isArray(value)) { + params.delete(key); + value.forEach(v => params.append(key, v)); + } else { + params.set(key, value); + } + }); + + return params; +} |