diff options
Diffstat (limited to 'frontend/src/components')
-rw-r--r-- | frontend/src/components/Connection.js | 18 | ||||
-rw-r--r-- | frontend/src/components/Connection.scss | 4 | ||||
-rw-r--r-- | frontend/src/components/ConnectionContent.js | 2 | ||||
-rw-r--r-- | frontend/src/components/ConnectionMatchedRules.js | 29 | ||||
-rw-r--r-- | frontend/src/components/ConnectionMatchedRules.scss | 23 | ||||
-rw-r--r-- | frontend/src/components/panels/ConfigurationPane.js | 162 | ||||
-rw-r--r-- | frontend/src/components/panels/ConfigurationPane.scss | 19 | ||||
-rw-r--r-- | frontend/src/components/panels/MainPane.js | 56 | ||||
-rw-r--r-- | frontend/src/components/panels/MainPane.scss | 23 | ||||
-rw-r--r-- | frontend/src/components/panels/PcapPane.js | 5 | ||||
-rw-r--r-- | frontend/src/components/panels/RulePane.js | 9 | ||||
-rw-r--r-- | frontend/src/components/panels/ServicePane.js | 2 |
12 files changed, 330 insertions, 22 deletions
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 { <span>Closed at {closedAt.toLocaleDateString() + " " + closedAt.toLocaleTimeString()}</span> </div>; - let classes = "connection"; - if (this.props.selected) { - classes += " connection-selected"; - } - if (this.props.containsFlag) { - classes += " contains-flag"; - } - const popoverFor = function (name, content) { return <Popover id={`popover-${name}-${conn.id}`} className="connection-popover"> <Popover.Content> @@ -88,7 +82,8 @@ class Connection extends Component { </div>; return ( - <tr className={classes}> + <tr className={classNames("connection", {"connection-selected": this.props.selected}, + {"has-matched-rules": conn.matched_rules.length > 0})}> <td> <span className="connection-service"> <ButtonField small fullSpan color={serviceColor} name={serviceName} @@ -99,6 +94,7 @@ class Connection extends Component { <td className="clickable" onClick={this.props.onSelected}>{conn.port_src}</td> <td className="clickable" onClick={this.props.onSelected}>{conn.ip_dst}</td> <td className="clickable" onClick={this.props.onSelected}>{conn.port_dst}</td> + <td className="clickable" onClick={this.props.onSelected}>{dateTimeToTime(conn.started_at)}</td> <td className="clickable" onClick={this.props.onSelected}> <OverlayTrigger trigger={["focus", "hover"]} placement="right" overlay={popoverFor("duration", timeInfo)}> @@ -107,7 +103,7 @@ class Connection extends Component { </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"> + <td> {/*<OverlayTrigger trigger={["focus", "hover"]} placement="right"*/} {/* overlay={popoverFor("hide", <span>Hide this connection from the list</span>)}>*/} {/* <span className={"connection-icon" + (conn.hidden ? " icon-enabled" : "")}*/} diff --git a/frontend/src/components/Connection.scss b/frontend/src/components/Connection.scss index cb9eb5f..97ef0a4 100644 --- a/frontend/src/components/Connection.scss +++ b/frontend/src/components/Connection.scss @@ -39,8 +39,8 @@ background-color: $color-primary-2; } - &.contains-flag { - border-right: 3px solid $color-secondary-2; + &.has-matched-rules { + border-bottom: 0; } } diff --git a/frontend/src/components/ConnectionContent.js b/frontend/src/components/ConnectionContent.js index 6bc0c96..ccaec0b 100644 --- a/frontend/src/components/ConnectionContent.js +++ b/frontend/src/components/ConnectionContent.js @@ -63,7 +63,7 @@ class ConnectionContent extends Component { } let unrollMap = (obj) => obj == null ? null : Object.entries(obj).map(([key, value]) => - <p><strong>{key}</strong>: {value}</p> + <p key={key}><strong>{key}</strong>: {value}</p> ); 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 <ButtonField key={mr} onClick={() => this.props.addMatchedRulesFilter(rule.id)} name={rule.name} + color={rule.color} small />; + }); + + return ( + <tr className="connection-matches"> + <td className="row-label">matched_rules:</td> + <td className="rule-buttons" colSpan={9}>{matchedRules}</td> + </tr> + ); + } +} + +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]) => + <tr key={username}> + <td>{username}</td> + <td><LinkPopover text="******" content={password} /></td> + <td><ButtonField variant="red" small rounded name="delete" + onClick={() => this.updateParam((s) => delete s.accounts[username]) }/></td> + </tr>).concat(<tr key={"new_account"}> + <td><InputField value={this.state.newUsername} small active={this.state.newUsernameActive} + onChange={(v) => this.setState({newUsername: v})} /></td> + <td><InputField value={this.state.newPassword} small active={this.state.newPasswordActive} + onChange={(v) => this.setState({newPassword: v})} /></td> + <td><ButtonField variant="green" small rounded name="add" onClick={this.addAccount}/></td> + </tr>); + + return ( + <div className="configuration-pane"> + <div className="pane"> + <div className="pane-container"> + <div className="pane-section"> + <div className="section-header"> + <span className="api-request">POST /setup</span> + <span className="api-response"><LinkPopover text={this.state.setupStatusCode} + content={this.state.setupResponse} + placement="left" /></span> + </div> + + <div className="section-content"> + <Container className="p-0"> + <Row> + <Col> + <InputField name="server_address" value={settings.config.server_address} + error={this.state.serverAddressError} + onChange={(v) => this.updateParam((s) => s.config.server_address = v)} /> + <InputField name="flag_regex" value={settings.config.flag_regex} + onChange={(v) => this.updateParam((s) => s.config.flag_regex = v)} + error={this.state.flagRegexError} /> + <div style={{"marginTop": "10px"}}> + <CheckField checked={settings.config.auth_required} name="auth_required" + onChange={(v) => this.updateParam((s) => s.config.auth_required = v)}/> + </div> + + </Col> + + <Col> + accounts: + <div className="section-table"> + <Table borderless size="sm"> + <thead> + <tr> + <th>username</th> + <th>password</th> + <th>actions</th> + </tr> + </thead> + <tbody> + {accounts} + </tbody> + </Table> + </div> + </Col> + </Row> + </Container> + + <TextField value={curlCommand} rows={4} readonly small={true}/> + </div> + + <div className="section-footer"> + <ButtonField variant="green" name="save" bordered onClick={this.saveSettings} /> + </div> + </div> + </div> + </div> + </div> + ); + } +} + +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 ( + <div className="main-pane"> + <div className="pane connections-pane"> + { + !this.state.loading && + <Connections onSelected={(c) => this.setState({selectedConnection: c})} + initialConnection={this.state.selectedConnection} /> + } + </div> + <div className="pane details-pane"> + <Switch> + <Route path="/pcaps" children={<PcapPane />} /> + <Route path="/rules" children={<RulePane />} /> + <Route path="/services" children={<ServicePane />} /> + <Route exact path="/connections/:id" children={<ConnectionContent connection={this.state.selectedConnection} />} /> + <Route children={<ConnectionContent />} /> + </Switch> + </div> + </div> + ); + } +} + +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 => - <tr className="table-row"> + <tr key={s.id} className="table-row"> <td>{s["id"].substring(0, 8)}</td> <td>{dateTimeToTime(s["started_at"])}</td> <td>{durationBetween(s["started_at"], s["completed_at"])}</td> @@ -119,8 +119,7 @@ class PcapPane extends Component { <td><LinkPopover text={Object.keys(s["packets_per_service"]).length + " services"} content={JSON.stringify(s["packets_per_service"])} placement="left"/></td> - <td className="table-cell-action"><a target="_blank" rel="noopener noreferrer" - href={"/api/pcap/sessions/" + s["id"] + "/download"}>download</a> + <td className="table-cell-action"><a href={"/api/pcap/sessions/" + s["id"] + "/download"}>download</a> </td> </tr> ); 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 => - <tr onClick={() => { + <tr key={r.id} onClick={() => { 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 ? - <tr> + <tr key={randomClassName()}> <td style={{"width": "500px"}}> <InputField small active={this.state.patternRegexFocused} value={pattern.regex} onChange={(v) => { @@ -272,7 +273,7 @@ class RulePane extends Component { </td> </tr> : - <tr className="row-small"> + <tr key={"new_pattern"} className="row-small"> <td>{p.regex}</td> <td>{p.flags.caseless ? "yes": "no"}</td> <td>{p.flags.dot_all ? "yes": "no"}</td> @@ -377,7 +378,7 @@ class RulePane extends Component { <thead> <tr> <th>regex</th> - <th>Aa</th> + <th>!Aa</th> <th>.*</th> <th>\n+</th> <th>UTF8</th> 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 => - <tr onClick={() => { + <tr key={s.port} onClick={() => { this.reset(); this.setState({isUpdate: true, currentService: _.cloneDeep(s)}); }} className={classNames("row-small", "row-clickable", {"row-selected": service.port === s.port })}> |