diff options
Diffstat (limited to 'frontend/src/views')
-rw-r--r-- | frontend/src/views/App.js | 58 | ||||
-rw-r--r-- | frontend/src/views/App.scss | 15 | ||||
-rw-r--r-- | frontend/src/views/Config.js | 242 | ||||
-rw-r--r-- | frontend/src/views/Config.scss | 55 | ||||
-rw-r--r-- | frontend/src/views/Connections.js | 42 | ||||
-rw-r--r-- | frontend/src/views/Filters.js | 1 | ||||
-rw-r--r-- | frontend/src/views/Header.js | 7 | ||||
-rw-r--r-- | frontend/src/views/MainPane.js | 59 | ||||
-rw-r--r-- | frontend/src/views/MainPane.scss | 6 | ||||
-rw-r--r-- | frontend/src/views/Services.js | 210 | ||||
-rw-r--r-- | frontend/src/views/Services.scss | 32 |
11 files changed, 67 insertions, 660 deletions
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; - } -} |