diff options
author | Emiliano Ciavatta | 2020-04-30 18:16:13 +0000 |
---|---|---|
committer | Emiliano Ciavatta | 2020-04-30 18:16:13 +0000 |
commit | 8db3b3b43bec6170a7f1db21772c48c5e270c97b (patch) | |
tree | 360d87fe5bda3790ef763dfe59b2dbfcce282ee6 /frontend/src | |
parent | 3f3ae121ef082a430f4bdc84ab7970a5633d552a (diff) |
Add services dialog
Diffstat (limited to 'frontend/src')
-rw-r--r-- | frontend/src/colors.scss | 8 | ||||
-rw-r--r-- | frontend/src/components/Connection.js | 78 | ||||
-rw-r--r-- | frontend/src/components/Connection.scss | 49 | ||||
-rw-r--r-- | frontend/src/components/ConnectionContent.js | 2 | ||||
-rw-r--r-- | frontend/src/index.scss | 114 | ||||
-rw-r--r-- | frontend/src/utils.js | 5 | ||||
-rw-r--r-- | frontend/src/views/App.js | 19 | ||||
-rw-r--r-- | frontend/src/views/Connections.js | 61 | ||||
-rw-r--r-- | frontend/src/views/Connections.scss | 32 | ||||
-rw-r--r-- | frontend/src/views/Header.js | 22 | ||||
-rw-r--r-- | frontend/src/views/MainPane.js | 10 | ||||
-rw-r--r-- | frontend/src/views/Services.js | 142 | ||||
-rw-r--r-- | frontend/src/views/Services.scss | 16 |
13 files changed, 400 insertions, 158 deletions
diff --git a/frontend/src/colors.scss b/frontend/src/colors.scss index 290a0cd..75f1828 100644 --- a/frontend/src/colors.scss +++ b/frontend/src/colors.scss @@ -9,3 +9,11 @@ $color-secondary-1: #A20A0A; $color-secondary-2: #DF3030; $color-secondary-3: #FF9D9D; $color-secondary-4: #FFDFDF; + +$color-blue: #247085; +$color-blue-light: #A5B8BE; +$color-blue-dark: #013B4C; + +$color-green: #25965D; +$color-green-light: #CDE4D8; +$color-green-dark: #004321;
\ No newline at end of file diff --git a/frontend/src/components/Connection.js b/frontend/src/components/Connection.js index 4bdc8cd..ce2b173 100644 --- a/frontend/src/components/Connection.js +++ b/frontend/src/components/Connection.js @@ -1,24 +1,13 @@ import React, {Component} from 'react'; import './Connection.scss'; -import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; -import { - faCloudDownloadAlt, - faCloudUploadAlt, - faComment, - faEyeSlash, - faHourglassHalf, - faLaptop, - faLink, - faServer, - faThumbtack, -} from '@fortawesome/free-solid-svg-icons' +import {Button, OverlayTrigger, Tooltip} from "react-bootstrap"; class Connection extends Component { render() { let conn = this.props.data - let serviceName = "assign" - let serviceColor = "#fff" - if (conn.service != null) { + let serviceName = "/dev/null" + let serviceColor = "#0F192E" + if (conn.service.port !== 0) { serviceName = conn.service.name serviceColor = conn.service.color } @@ -27,41 +16,40 @@ class Connection extends Component { let duration = ((closedAt - startedAt) / 1000).toFixed(3) let timeInfo = `Started at ${startedAt}\nClosed at ${closedAt}\nProcessed at ${new Date(conn.processed_at)}` + let classes = "connection" + if (this.props.selected) { + classes += " connection-selected" + } + if (conn.marked){ + classes += " connection-marked" + } return ( - <tr className={conn.marked ? "connection connection-marked" : "connection"}> - <div className="connection-header"> + <tr className={classes}> + <td> <span className="connection-service"> - <button className="btn" style={{ + <Button size="sm" style={{ "backgroundColor": serviceColor - }}>{serviceName}</button> - </span> - <span className="connection-src"> - <FontAwesomeIcon icon={faLaptop}/> - <span className="connection-ip-port">{conn.ip_src}:{conn.port_src}</span> - </span> - <span className="connection-separator">{"->"}</span> - <span className="connection-dst"> - <FontAwesomeIcon icon={faServer}/> - <span className="connection-ip-port">{conn.ip_dst}:{conn.port_dst}</span> + }}>{serviceName}</Button> </span> - - <span className="connection-duration" data-toggle="tooltip" data-placement="top" title={timeInfo}> - <FontAwesomeIcon icon={faHourglassHalf}/> - <span className="connection-seconds">{duration}s</span> - </span> - <span className="connection-bytes"> - <FontAwesomeIcon icon={faCloudDownloadAlt}/> - <span className="connection-bytes-count">{conn.client_bytes}</span> - <FontAwesomeIcon icon={faCloudUploadAlt}/> - <span className="connection-bytes-count">{conn.server_bytes}</span> - </span> - <span className="connection-hide"><FontAwesomeIcon icon={faEyeSlash}/></span> - <span className="connection-mark"><FontAwesomeIcon icon={faThumbtack}/></span> - <span className="connection-comment"><FontAwesomeIcon icon={faComment}/></span> - <span className="connection-link"><FontAwesomeIcon icon={faLink}/></span> - </div> - + </td> + <td className="clickable" onClick={() => this.props.onSelected()}>{conn.ip_src}</td> + <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()}> + {/*<OverlayTrigger placement="top" overlay={<Tooltip id={`tooltip-${conn.id}`}>{timeInfo}</Tooltip>}>*/} + <span className="test-tooltip">{duration}s</span> + {/*</OverlayTrigger>*/} + </td> + <td className="clickable" onClick={() => this.props.onSelected()}>{conn.client_bytes}</td> + <td className="clickable" onClick={() => this.props.onSelected()}>{conn.server_bytes}</td> + <td> + <span className="connection-icon connection-hide">%</span> + <span className="connection-icon connection-mark">!!</span> + <span className="connection-icon connection-comment">@</span> + <span className="connection-icon connection-link">#</span> + </td> </tr> ); diff --git a/frontend/src/components/Connection.scss b/frontend/src/components/Connection.scss index 8910cb4..d27ebc8 100644 --- a/frontend/src/components/Connection.scss +++ b/frontend/src/components/Connection.scss @@ -2,52 +2,41 @@ .connection { background-color: $color-primary-0; - margin-bottom: 5px; + border-top: 3px solid $color-primary-3; + border-bottom: 3px solid $color-primary-3; + td { + vertical-align: middle; + } &.connection-marked { border-right: 5px solid $color-secondary-2; } .connection-service { - padding: 0 12px 0 0; - .btn { - font-size: 12px; - width: 120px; + width: 100%; } - } - - .connection-separator { - margin: 0 10px; - } - - .connection-duration { - margin-left: 15px; - //.connection-seconds { - // width: 75px; - // display: inline-block; - // text-align: right; - //} - } - - .connection-bytes { - margin-left: 10px; - margin-right: 10px; - - .connection-bytes-count { - margin-right: 5px; + .btn:hover { + background-color: $color-primary-1 !important; + color: $color-primary-4; } } - .connection-hide, .connection-mark, .connection-comment, .connection-link { - margin-left: 5px; + .connection-icon { + font-weight: 600; + font-size: 18px; + margin-right: 6px; + cursor: pointer; } + &:hover { + background-color: $color-primary-2; + } - svg { - margin-right: 6px; + &.connection-selected { + background-color: $color-primary-2; } } diff --git a/frontend/src/components/ConnectionContent.js b/frontend/src/components/ConnectionContent.js index 061282c..bd35b5c 100644 --- a/frontend/src/components/ConnectionContent.js +++ b/frontend/src/components/ConnectionContent.js @@ -11,8 +11,6 @@ class ConnectionContent extends Component { render() { let content = this.props.connectionPayload - console.log(content) - if (content === undefined) { return <div>nope</div> } diff --git a/frontend/src/index.scss b/frontend/src/index.scss index 5c18095..def3fdf 100644 --- a/frontend/src/index.scss +++ b/frontend/src/index.scss @@ -21,41 +21,64 @@ pre { font-size: 14px; } -button { - :focus { - outline: none; - box-shadow: none; - } -} .btn { border-radius: 0; + background-color: $color-primary-2; + border: none; + border-bottom: 5px solid $color-primary-1; color: $color-primary-4; + outline: none; + padding: 5px 12px; + font-weight: 500; + + &:hover, &:active { + background-color: $color-primary-1; + color: $color-primary-4; + } + + &:focus, &:active { + outline: none !important; + box-shadow: none !important; + } } -.btn-primary { - margin-left: 20px; - padding: 5px 12px; +.btn-sm { + border: none; + font-size: 12px; +} + +.btn-red { + color: $color-secondary-4; background-color: $color-secondary-2; - font-weight: 500; - border: 0; border-bottom: 5px solid $color-secondary-1; - color: $color-secondary-4; - outline:none; + &:hover, &:active { + color: $color-secondary-4; + background-color: $color-secondary-1; + } +} +.btn-blue { + color: $color-blue-light; + background-color: $color-blue; + border-bottom: 5px solid $color-blue-dark; - :focus, :active { - outline: none; - box-shadow: none; + &:hover, &:active { + color: $color-blue-light; + background-color: $color-blue-dark; } - } -.btn-primary:hover { - background-color: $color-secondary-1; - border-color: $color-secondary-1; - outline: none; +.btn-green { + color: $color-green-light; + background-color: $color-green; + border-bottom: 5px solid $color-green-dark; + + &:hover, &:active { + color: $color-green-light; + background-color: $color-green-dark; + } } a { @@ -85,4 +108,53 @@ a { /* Handle on hover */ ::-webkit-scrollbar-thumb:hover { background: $color-secondary-2; +} + +.clickable { + cursor: pointer; +} + +.modal-content { + background-color: $color-primary-0; + + .modal-header { + background-color: $color-primary-2; + border: none; + } + + .modal-footer { + border: none; + } +} + +input.form-control, textarea.form-control { + background-color: $color-primary-2; + border: none; + color: $color-primary-4; + + &:focus { + background-color: $color-primary-1; + color: $color-primary-4; + box-shadow: none; + } + + &[readonly] { + background-color: $color-primary-2; + border: none; + color: $color-primary-4; + } + + &[readonly]:focus { + background-color: $color-primary-1; + color: $color-primary-4; + box-shadow: none; + } +} + +textarea.form-control { + resize: none; +} + +.table { + color: $color-primary-4; }
\ No newline at end of file diff --git a/frontend/src/utils.js b/frontend/src/utils.js new file mode 100644 index 0000000..2487f09 --- /dev/null +++ b/frontend/src/utils.js @@ -0,0 +1,5 @@ + +export function createCurlCommand(subCommand, data) { + return `curl --request PUT \\\n --url ${window.location.hostname}/api${subCommand} \\\n ` + + `--header 'content-type: application/json' \\\n --data '${JSON.stringify(data)}'` +} diff --git a/frontend/src/views/App.js b/frontend/src/views/App.js index 4d80da5..2b444a2 100644 --- a/frontend/src/views/App.js +++ b/frontend/src/views/App.js @@ -4,20 +4,35 @@ import './App.scss'; import MainPane from "./MainPane"; import Footer from "./Footer"; import {Route, BrowserRouter as Router, Switch} from "react-router-dom"; +import Services from "./Services"; class App extends Component { + constructor(props) { + super(props); + this.state = { + servicesShow: false + }; + } + render() { + let modal = "" + if (this.state.servicesShow) { + modal = <Services onHide={() => this.setState({servicesShow: false})} /> + } + return ( <div className="app"> <Router> - <Header/> + <Header onOpenServices={() => this.setState({servicesShow: true})}/> <Switch> + <Route path="/connections/:id" children={<MainPane/>} /> <Route path="/" children={<MainPane/>} /> - <Route path="/connection/:id" children={<MainPane/>} /> </Switch> + {modal} <Footer/> </Router> + </div> ); } diff --git a/frontend/src/views/Connections.js b/frontend/src/views/Connections.js index 5876a40..fa7798e 100644 --- a/frontend/src/views/Connections.js +++ b/frontend/src/views/Connections.js @@ -2,8 +2,8 @@ import React, {Component} from 'react'; import './Connections.scss'; import axios from 'axios' import Connection from "../components/Connection"; -import {Link} from "react-router-dom"; import Table from 'react-bootstrap/Table'; +import {Redirect} from 'react-router'; class Connections extends Component { constructor(props) { @@ -19,34 +19,16 @@ class Connections extends Component { } render() { - let connection = { - "id": "5dd95ff0fe7ae01ae7f419c2", - "ip_src": "10.62.82.1", - "ip_dst": "10.62.82.2", - "port_src": 59113, - "port_dst": 23179, - "started_at": "2019-11-23T16:36:00.1Z", - "closed_at": "2019-11-23T16:36:00.971Z", - "client_bytes": 331, - "server_bytes": 85, - "client_documents": 1, - "server_documents": 1, - "processed_at": "2020-04-21T17:10:29.532Z", - "matched_rules": [], - "hidden": false, - "marked": true, - "comment": "", - "service": { - "port": 23179, - "name": "kaboom", - "color": "#3C6D3C", - "notes": "wdddoddddddw" - } + let redirect = "" + if (this.state.selected) { + redirect = <Redirect push to={"/connections/" + this.state.selected} />; } return ( + <div className="connections"> - <Table striped hover> + <div className="connections-header-padding"/> + <Table borderless size="sm"> <thead> <tr> <th>service</th> @@ -61,31 +43,16 @@ class Connections extends Component { </tr> </thead> <tbody> - <tr> - <td>1</td> - <td>Mark</td> - <td>Otto</td> - <td>@mdo</td> - </tr> - <tr> - <td>2</td> - <td>Jacob</td> - <td>Thornton</td> - <td>@fat</td> - </tr> - <tr> - <td>3</td> - <td colSpan="2">Larry the Bird</td> - <td>@twitter</td> - </tr> + { + this.state.connections.map(c => + <Connection key={c.id} data={c} onSelected={() => this.setState({selected: c.id})} + selected={this.state.selected === c.id}/> + ) + } </tbody> </Table> - { - this.state.connections.map(c => - <Link to={"/connection/" + c.id}><Connection data={c} /></Link> - ) - } + {redirect} </div> ); } diff --git a/frontend/src/views/Connections.scss b/frontend/src/views/Connections.scss index da74e11..6d2de3f 100644 --- a/frontend/src/views/Connections.scss +++ b/frontend/src/views/Connections.scss @@ -2,12 +2,40 @@ .connections { background-color: $color-primary-3; - padding: 10px 10px 10px 10px; + padding: 0 10px; + position: relative; height: 100%; overflow: auto; .table { - color: $color-primary-4; + margin-top: 10px; } + + .connections-header-padding { + position: sticky; + height: 10px; + background-color: $color-primary-3; + top: 0; + left: 0; + right: 0; + margin-bottom: -10px; + } + + th { + background-color: $color-primary-2; + border-top: 3px solid $color-primary-3; + border-bottom: 3px solid $color-primary-3; + font-size: 13.5px; + + position: sticky; + top: 10px; + padding: 5px; + } + + &:hover::-webkit-scrollbar-thumb { + background: $color-secondary-2; + } + + }
\ No newline at end of file diff --git a/frontend/src/views/Header.js b/frontend/src/views/Header.js index b3f919b..ac272e6 100644 --- a/frontend/src/views/Header.js +++ b/frontend/src/views/Header.js @@ -1,9 +1,17 @@ import React, {Component} from 'react'; import Typed from 'typed.js'; import './Header.scss'; +import {Button} from "react-bootstrap"; class Header extends Component { + constructor(props) { + super(props); + this.state = { + servicesShow: false + }; + } + componentDidMount() { const options = { strings: ["caronte$ "], @@ -28,15 +36,11 @@ class Header extends Component { </div> <div className="col"> <div className="header-buttons"> - <button className="btn-primary"> - ➕ pcaps - </button> - <button className="btn-primary"> - ➕ rules - </button> - <button className="btn-primary"> - ➕ services - </button> + <Button variant="yellow" size="sm">pcaps</Button> + <Button variant="blue">rules</Button> + <Button variant="red" onClick={this.props.onOpenServices}> + services + </Button> </div> </div> </div> diff --git a/frontend/src/views/MainPane.js b/frontend/src/views/MainPane.js index 88b5376..0fc083e 100644 --- a/frontend/src/views/MainPane.js +++ b/frontend/src/views/MainPane.js @@ -26,6 +26,16 @@ class MainPane extends Component { } componentDidMount() { + if (this.props.match.params.id !== this.state.id) { + const id = this.props.match.params.id; + this.setState({id: id}); + + axios.get(`/api/streams/${id}`).then(res => this.setState({connectionContent: res.data})) + + + } + + } render() { diff --git a/frontend/src/views/Services.js b/frontend/src/views/Services.js new file mode 100644 index 0000000..1b3789d --- /dev/null +++ b/frontend/src/views/Services.js @@ -0,0 +1,142 @@ +import React, {Component} from 'react'; +import './Services.scss'; +import {Button, ButtonGroup, Col, Container, Form, FormControl, InputGroup, Modal, Row, Table} from "react-bootstrap"; +import axios from 'axios' +import {createCurlCommand} from '../utils'; + +class Services extends Component { + + constructor(props) { + super(props); + this.state = { + services: {}, + port: "", + portValid: false + } + + this.portChanged = this.portChanged.bind(this); + } + + componentDidMount() { + axios.get("/api/services").then(res => this.setState({services: res.data})) + } + + portChanged(event) { + let value = event.target.value.replace(/[^\d]/gi, '') + let intValue = parseInt(value) + this.setState({port: value, portValid: intValue > 0 && intValue <= 65565}) + + + } + + + render() { + let curl = createCurlCommand("/services", { + "port": this.state.port, + "name": "aaaaa", + "color": "#fff", + "notes": "aaa" + }) + + let rows = Object.values(this.state.services).map(s => + <tr> + <td><Button size="sm" style={{ + "backgroundColor": s.color + }}>edit</Button></td> + <td>{s.port}</td> + <td>{s.name}</td> + </tr> + ) + + + + + return ( + <Modal + {...this.props} + show="true" + size="lg" + aria-labelledby="contained-modal-title-vcenter" + centered + > + <Modal.Header> + <Modal.Title id="contained-modal-title-vcenter"> + ~/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">new</Button></th> + <th>name</th> + <th>port</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.Text className="text-muted"> + {!this.state.portValid ? "assert(1 <= port <= 65565)" : ""} + </Form.Text> + </Form.Group> + + <Form.Group controlId="serviceName"> + <Form.Label>name:</Form.Label> + <Form.Control type="text" required min={3} max={16} /> + <Form.Text className="text-muted"> + {"assert(len(name) >= 3)"} + </Form.Text> + </Form.Group> + + <Form.Group controlId="serviceColor"> + <Form.Label>color:</Form.Label> + <ButtonGroup aria-label="Basic example"> + <Button variant="secondary">Left</Button> + <Button variant="secondary">Middle</Button> + <Button variant="secondary">Right</Button> + </ButtonGroup> + </Form.Group> + + <Form.Group controlId="serviceNotes"> + <Form.Label>notes:</Form.Label> + <Form.Control type="textarea" /> + </Form.Group> + </Form> + + + </Col> + + </Row> + + <Row> + <Col md={12}> + <InputGroup> + <FormControl as="textarea" rows={9} className="curl-output" readOnly={true}>{curl} + </FormControl> + </InputGroup> + + </Col> + </Row> + </Container> + </Modal.Body> + <Modal.Footer className="dialog-footer"> + <Button variant="green" onClick={this.props.onHide}>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 new file mode 100644 index 0000000..fd65beb --- /dev/null +++ b/frontend/src/views/Services.scss @@ -0,0 +1,16 @@ +.curl-output { + width: 100%; + font-size: 13px; +} + +.services-list { + .btn { + width: 150px; + } +} + +.dialog-footer { + .btn { + width: 80px; + } +}
\ No newline at end of file |