diff options
27 files changed, 489 insertions, 792 deletions
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 { <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 })}> 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 = <Services onHide={() => this.setState({servicesWindowOpen: false})}/>; - } - if (this.state.filterWindowOpen) { + if (this.state.filterWindowOpen && this.state.configured) { modal = <Filters onHide={() => this.setState({filterWindowOpen: false})}/>; } - if (this.state.configWindowOpen) { - modal = <Config onHide={() => this.setState({configWindowOpen: false})} - onDone={() => this.setState({configDone: true})}/>; - } return ( - <div className="app"> + <div className="main"> <Router> - <Header onOpenServices={() => this.setState({servicesWindowOpen: true})} - onOpenFilters={() => this.setState({filterWindowOpen: true})} - onOpenConfig={() => this.setState({configWindowOpen: true})} - onOpenUpload={() => this.setState({uploadWindowOpen: true})} - onConfigDone={this.state.configDone} - /> - <MainPane /> - {modal} - <Footer/> + <div className="main-header"> + <Header onOpenFilters={() => this.setState({filterWindowOpen: true})} /> + </div> + <div className="main-content"> + {this.state.configured ? <MainPane /> : + <ConfigurationPane onConfigured={() => this.setState({configured: true})} />} + {modal} + </div> + <div className="main-footer"> + {this.state.configured && <Footer/>} + </div> </Router> - </div> ); } 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 => - <tr> - <td>{u}</td> - </tr> - ); - - - - return ( - <> - <Modal show={this.state.showSignup} size="lg" aria-labelledby="services-dialog" centered > - <Modal.Header> - <Modal.Title id="services-dialog"> - # passwd - </Modal.Title> - </Modal.Header> - <Modal.Body> - <Container> - <Row> - <Form id="passwd-form"> - <Form.Group controlId="username"> - <Form.Label>username:</Form.Label> - <Form.Control type="text" onChange={this.userChanged} value={this.state.tmpUser}/> - </Form.Group> - - <Form.Group controlId="password"> - <Form.Label>password:</Form.Label> - <Form.Control type="password" onChange={this.passwdChanged} value={this.state.tmpPass}/> - </Form.Group> - - <Form.Group controlId="confirmPassword"> - <Form.Label>confirm password:</Form.Label> - <Form.Control type="password" onChange={this.confirmChanged} value={this.state.tmpConf}/> - </Form.Group> - - - </Form> - - </Row> - - </Container> - </Modal.Body> - <Modal.Footer className="dialog-footer"> - <Button variant="green" onClick={() => this.signup()}>signup</Button> - <Button variant="red" onClick={() => this.setState({showSignup:false,showConfig:true})}>close</Button> - </Modal.Footer> - - </Modal> - <Modal - {...this.props} - show="true" - size="lg" - aria-labelledby="services-dialog" - centered - > - <Modal.Header> - <Modal.Title id="services-dialog"> - ~/.config - </Modal.Title> - </Modal.Header> - <Modal.Body> - <div class="blink"><span><b>Warning:</b></span> once the configuration is completed, it cannot be changed unless you reset caronte :(</div> - <hr/> - <Container> - <Row> - <Col md={5}> - - <ButtonGroup toggle className="mb-2"> - <ToggleButton - type="checkbox" - variant="secondary" - checked={this.state.auth_required} - value={this.state.auth_required} - onChange={() => this.authRequiredChanged()} - > - Authentication - </ToggleButton> - </ButtonGroup> - - <Table borderless size="sm" className="users-list"> - - <thead> - <tr> - <th>users</th> - </tr> - </thead> - <tbody> - {rows} - <tr> <td> - <Button size="sm" onClick={() => this.setState({showSignup:true,showConfig:false})}>new</Button> - </td> </tr> - </tbody> - </Table> - - - - </Col> - - <Col md={7}> - - <Form> - <Form.Group controlId="server_address"> - <Form.Label>server_address:</Form.Label> - <Form.Control type="text" onChange={this.serverIpChanged} value={this.state.server_address}/> - </Form.Group> - - <Form.Group controlId="flag_regex"> - <Form.Label>flag_regex:</Form.Label> - <Form.Control type="text" onChange={this.flagRegexChanged} value={this.state.flag_regex}/> - </Form.Group> - - </Form> - - </Col> - - </Row> - <Row> - <div class="error"> - <b> - {this.state.errors - .split('\n').map((item, key) => { - return <span key={key}>{item}<br/></span>}) - } - </b> - </div> - </Row> - - </Container> - </Modal.Body> - <Modal.Footer className="dialog-footer"> - <Button variant="green" onClick={() => this.setup()}>set</Button> - <Button variant="red" onClick={this.props.onHide}>close</Button> - </Modal.Footer> - </Modal> - </> - ); - } -} - -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 { <th>srcport</th> <th>dstip</th> <th>dstport</th> + <th>started_at</th> <th>duration</th> <th>up</th> <th>down</th> @@ -166,13 +173,18 @@ class Connections extends Component { </thead> <tbody> { - this.state.connections.map(c => - <Connection key={c.id} data={c} onSelected={() => 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 [<Connection key={c.id} data={c} onSelected={() => 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 && + <ConnectionMatchedRules key={c.id + "_m"} matchedRules={c.matched_rules} + rules={this.state.rules} + addMatchedRulesFilter={this.addMatchedRulesFilter} /> + ]; + }) } {loading} </tbody> 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 => <React.Fragment key={name} >{filtersDefinitions[name]}</React.Fragment>) .slice(0, 5); return ( @@ -78,8 +78,9 @@ class Header extends Component { <Link to="/services"> <ButtonField variant="indigo" name="services" bordered /> </Link> - <ButtonField variant="blue" onClick={this.props.onOpenConfig} - disabled={false} name="config" bordered /> + <Link to="/config"> + <ButtonField variant="blue" name="config" bordered /> + </Link> </div> </div> </div> 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 ( - <div className="main-pane"> - <div className="container-fluid"> - <div className="row"> - <div className="col-md-6 pane"> - { - !this.state.loading && - <Connections onSelected={(c) => this.setState({selectedConnection: c})} - initialConnection={this.state.selectedConnection} /> - } - </div> - <div className="col-md-6 pl-0 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> - </div> - </div> - ); - } -} - -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 => - <tr> - <td><Button variant="btn-edit" size="sm" - onClick={() => this.editService(s)} style={{"backgroundColor": s.color}}>edit</Button></td> - <td>{s.port}</td> - <td>{s.name}</td> - </tr> - ); - - let colorButtons = this.colors.map((color, i) => - <Button size="sm" className="btn-color" key={"button" + this.alphabet[i]} - style={{"backgroundColor": color, "borderColor": this.state.color === color ? "#fff" : color}} - onClick={() => this.setState({color: color})}>{this.alphabet[i]}</Button>); - - return ( - <Modal - {...this.props} - show="true" - size="lg" - aria-labelledby="services-dialog" - centered - > - <Modal.Header> - <Modal.Title id="services-dialog"> - ~/services - </Modal.Title> - </Modal.Header> - <Modal.Body> - <Container> - <Row> - <Col md={7}> - <Table borderless size="sm" className="services-list"> - <thead> - <tr> - <th><Button size="sm" onClick={this.newService}>new</Button></th> - <th>port</th> - <th>name</th> - </tr> - </thead> - <tbody> - {rows} - </tbody> - </Table> - </Col> - <Col md={5}> - <Form> - <Form.Group controlId="servicePort"> - <Form.Label>port:</Form.Label> - <Form.Control type="text" onChange={this.portChanged} value={this.state.port}/> - </Form.Group> - - <Form.Group controlId="serviceName"> - <Form.Label>name:</Form.Label> - <Form.Control type="text" onChange={this.nameChanged} value={this.state.name}/> - </Form.Group> - - <Form.Group controlId="serviceColor"> - <Form.Label>color:</Form.Label> - <ButtonGroup aria-label="Basic example"> - {colorButtons.slice(0, 8)} - </ButtonGroup> - <ButtonGroup aria-label="Basic example"> - {colorButtons.slice(8, 18)} - </ButtonGroup> - </Form.Group> - - <Form.Group controlId="serviceNotes"> - <Form.Label>notes:</Form.Label> - <Form.Control as="textarea" rows={3} onChange={this.notesChanged} - value={this.state.notes}/> - </Form.Group> - </Form> - - - </Col> - - </Row> - - <Row> - <Col md={12}> - <InputGroup> - <FormControl as="textarea" rows={4} className="curl-output" readOnly={true} - value={output}/> - </InputGroup> - - </Col> - </Row> - </Container> - </Modal.Body> - <Modal.Footer className="dialog-footer"> - <Button variant="green" onClick={this.saveService}>save</Button> - <Button variant="red" onClick={this.props.onHide}>close</Button> - </Modal.Footer> - </Modal> - ); - } -} - -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}, |