diff options
author | Emiliano Ciavatta | 2020-10-07 12:58:48 +0000 |
---|---|---|
committer | Emiliano Ciavatta | 2020-10-07 12:58:48 +0000 |
commit | d5f94b76986615b255b77b2a7b7ed336e5ad4838 (patch) | |
tree | c813c55845be273efccf60995f43a77fdee68ac8 /frontend/src/components | |
parent | e905618113309eaba7227ff1328a20f6846e4afd (diff) |
Implement notifications
Diffstat (limited to 'frontend/src/components')
-rw-r--r-- | frontend/src/components/Connection.js | 11 | ||||
-rw-r--r-- | frontend/src/components/ConnectionContent.scss | 9 | ||||
-rw-r--r-- | frontend/src/components/Notifications.js | 60 | ||||
-rw-r--r-- | frontend/src/components/Notifications.scss | 48 | ||||
-rw-r--r-- | frontend/src/components/panels/PcapPane.js | 33 | ||||
-rw-r--r-- | frontend/src/components/panels/RulePane.js | 116 | ||||
-rw-r--r-- | frontend/src/components/panels/ServicePane.js | 35 | ||||
-rw-r--r-- | frontend/src/components/panels/common.scss | 4 |
8 files changed, 219 insertions, 97 deletions
diff --git a/frontend/src/components/Connection.js b/frontend/src/components/Connection.js index 44f9f18..46a0cab 100644 --- a/frontend/src/components/Connection.js +++ b/frontend/src/components/Connection.js @@ -48,10 +48,11 @@ class Connection extends Component { render() { let conn = this.props.data; let serviceName = "/dev/null"; - let serviceColor = "#0F192E"; - if (conn.service.port !== 0) { - serviceName = conn.service.name; - serviceColor = conn.service.color; + let serviceColor = "#0f192e"; + if (this.props.services[conn["port_dst"]]) { + const service = this.props.services[conn["port_dst"]]; + serviceName = service.name; + serviceColor = service.color; } let startedAt = new Date(conn.started_at); let closedAt = new Date(conn.closed_at); @@ -87,7 +88,7 @@ class Connection extends Component { <td> <span className="connection-service"> <ButtonField small fullSpan color={serviceColor} name={serviceName} - onClick={() => this.props.addServicePortFilter(conn.port_dst)} /> + onClick={() => this.props.addServicePortFilter(conn.port_dst)}/> </span> </td> <td className="clickable" onClick={this.props.onSelected}>{conn.ip_src}</td> diff --git a/frontend/src/components/ConnectionContent.scss b/frontend/src/components/ConnectionContent.scss index de4d699..f4edec9 100644 --- a/frontend/src/components/ConnectionContent.scss +++ b/frontend/src/components/ConnectionContent.scss @@ -2,7 +2,6 @@ .connection-content { height: 100%; - padding: 10px 10px 0; background-color: $color-primary-0; pre { @@ -91,12 +90,12 @@ .connection-content-header { height: 33px; padding: 0; - background-color: $color-primary-2; + background-color: $color-primary-3; .header-info { font-size: 12px; padding-top: 7px; - padding-left: 20px; + padding-left: 25px; } .header-actions { @@ -104,6 +103,10 @@ .choice-field { margin-top: -5px; + + .field-value { + background-color: $color-primary-3; + } } } } diff --git a/frontend/src/components/Notifications.js b/frontend/src/components/Notifications.js new file mode 100644 index 0000000..4d6dcd4 --- /dev/null +++ b/frontend/src/components/Notifications.js @@ -0,0 +1,60 @@ +import React, {Component} from 'react'; +import './Notifications.scss'; +import dispatcher from "../dispatcher"; + +const _ = require('lodash'); +const classNames = require('classnames'); + +class Notifications extends Component { + + state = { + notifications: [], + closedNotifications: [], + }; + + componentDidMount() { + dispatcher.register("notifications", notification => { + const notifications = this.state.notifications; + notifications.push(notification); + this.setState({notifications}); + + setTimeout(() => { + const notifications = this.state.notifications; + notification.open = true; + this.setState({notifications}); + }, 100); + + setTimeout(() => { + const notifications = _.without(this.state.notifications, notification); + const closedNotifications = this.state.closedNotifications.concat([notification]); + notification.closed = true; + this.setState({notifications, closedNotifications}); + }, 5000); + + setTimeout(() => { + const closedNotifications = _.without(this.state.closedNotifications, notification); + this.setState({closedNotifications}); + }, 6000); + }); + } + + render() { + return ( + <div className="notifications"> + <div className="notifications-list"> + { + this.state.closedNotifications.concat(this.state.notifications).map(n => + <div className={classNames("notification", {"notification-closed": n.closed}, + {"notification-open": n.open})}> + <h3 className="notification-title">{n.event}</h3> + <span className="notification-description">{JSON.stringify(n.message)}</span> + </div> + ) + } + </div> + </div> + ); + } +} + +export default Notifications; diff --git a/frontend/src/components/Notifications.scss b/frontend/src/components/Notifications.scss new file mode 100644 index 0000000..b0c334b --- /dev/null +++ b/frontend/src/components/Notifications.scss @@ -0,0 +1,48 @@ +@import "../colors.scss"; + +.notifications { + position: absolute; + + left: 30px; + bottom: 50px; + z-index: 50; + + .notifications-list { + + } + + .notification { + background-color: $color-green; + border-left: 5px solid $color-green-dark; + padding: 10px; + margin: 10px 0; + width: 250px; + color: $color-green-light; + transform: translateX(-300px); + transition: all 1s ease; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + .notification-title { + font-size: 0.9em; + margin: 0; + } + + .notification-description { + font-size: 0.8em; + } + + &.notification-open { + transform: translateX(0px); + } + + &.notification-closed { + transform: translateY(-50px); + opacity: 0; + } + + } + + +}
\ No newline at end of file diff --git a/frontend/src/components/panels/PcapPane.js b/frontend/src/components/panels/PcapPane.js index 31d8815..13f7cb3 100644 --- a/frontend/src/components/panels/PcapPane.js +++ b/frontend/src/components/panels/PcapPane.js @@ -9,28 +9,31 @@ import CheckField from "../fields/CheckField"; import TextField from "../fields/TextField"; import ButtonField from "../fields/ButtonField"; import LinkPopover from "../objects/LinkPopover"; +import dispatcher from "../../dispatcher"; class PcapPane extends Component { - constructor(props) { - super(props); - - this.state = { - sessions: [], - isUploadFileValid: true, - isUploadFileFocused: false, - uploadFlushAll: false, - isFileValid: true, - isFileFocused: false, - fileValue: "", - processFlushAll: false, - deleteOriginalFile: false - }; - } + state = { + sessions: [], + isUploadFileValid: true, + isUploadFileFocused: false, + uploadFlushAll: false, + isFileValid: true, + isFileFocused: false, + fileValue: "", + processFlushAll: false, + deleteOriginalFile: false + }; componentDidMount() { this.loadSessions(); + dispatcher.register("notifications", payload => { + if (payload.event === "pcap.upload" || payload.event === "pcap.file") { + this.loadSessions(); + } + }); + document.title = "caronte:~/pcaps$"; } diff --git a/frontend/src/components/panels/RulePane.js b/frontend/src/components/panels/RulePane.js index 4641378..76f3ac0 100644 --- a/frontend/src/components/panels/RulePane.js +++ b/frontend/src/components/panels/RulePane.js @@ -14,35 +14,13 @@ import ButtonField from "../fields/ButtonField"; import validation from "../../validation"; import LinkPopover from "../objects/LinkPopover"; import {randomClassName} from "../../utils"; +import dispatcher from "../../dispatcher"; const classNames = require('classnames'); const _ = require('lodash'); class RulePane extends Component { - constructor(props) { - super(props); - - this.state = { - rules: [], - newRule: this.emptyRule, - newPattern: this.emptyPattern - }; - - this.directions = { - 0: "both", - 1: "c->s", - 2: "s->c" - }; - } - - componentDidMount() { - this.reset(); - this.loadRules(); - - document.title = "caronte:~/rules$"; - } - emptyRule = { "name": "", "color": "", @@ -60,7 +38,6 @@ class RulePane extends Component { }, "version": 0 }; - emptyPattern = { "regex": "", "flags": { @@ -74,6 +51,34 @@ class RulePane extends Component { "max_occurrences": 0, "direction": 0 }; + state = { + rules: [], + newRule: this.emptyRule, + newPattern: this.emptyPattern + }; + + constructor(props) { + super(props); + + this.directions = { + 0: "both", + 1: "c->s", + 2: "s->c" + }; + } + + componentDidMount() { + this.reset(); + this.loadRules(); + + dispatcher.register("notifications", payload => { + if (payload.event === "rules.new" || payload.event === "rules.edit") { + this.loadRules(); + } + }); + + document.title = "caronte:~/rules$"; + } loadRules = () => { backend.get("/api/rules").then(res => this.setState({rules: res.json, rulesStatusCode: res.status})) @@ -226,17 +231,17 @@ class RulePane extends Component { <tr key={r.id} onClick={() => { this.reset(); this.setState({selectedRule: _.cloneDeep(r)}); - }} className={classNames("row-small", "row-clickable", {"row-selected": rule.id === r.id })}> + }} className={classNames("row-small", "row-clickable", {"row-selected": rule.id === r.id})}> <td>{r["id"].substring(0, 8)}</td> <td>{r["name"]}</td> - <td><ButtonField name={r["color"]} color={r["color"]} small /></td> + <td><ButtonField name={r["color"]} color={r["color"]} small/></td> <td>{r["notes"]}</td> </tr> ); let patterns = (this.state.selectedPattern == null && !isUpdate ? - rule.patterns.concat(this.state.newPattern) : - rule.patterns + rule.patterns.concat(this.state.newPattern) : + rule.patterns ).map(p => p === pattern ? <tr key={randomClassName()}> <td style={{"width": "500px"}}> @@ -244,7 +249,7 @@ class RulePane extends Component { onChange={(v) => { this.updateParam(() => pattern.regex = v); this.setState({patternRegexFocused: pattern.regex === ""}); - }} /> + }}/> </td> <td><CheckField small checked={pattern.flags.caseless} onChange={(v) => this.updateParam(() => pattern.flags.caseless = v)}/></td> @@ -259,34 +264,35 @@ class RulePane extends Component { <td style={{"width": "70px"}}> <NumericField small value={pattern.min_occurrences} active={this.state.patternOccurrencesFocused} - onChange={(v) => this.updateParam(() => pattern.min_occurrences = v)} /> + onChange={(v) => this.updateParam(() => pattern.min_occurrences = v)}/> </td> <td style={{"width": "70px"}}> <NumericField small value={pattern.max_occurrences} active={this.state.patternOccurrencesFocused} - onChange={(v) => this.updateParam(() => pattern.max_occurrences = v)} /> + onChange={(v) => this.updateParam(() => pattern.max_occurrences = v)}/> </td> <td><ChoiceField inline small keys={[0, 1, 2]} values={["both", "c->s", "s->c"]} value={this.directions[pattern.direction]} - onChange={(v) => this.updateParam(() => pattern.direction = v)} /></td> + onChange={(v) => this.updateParam(() => pattern.direction = v)}/></td> <td>{this.state.selectedPattern == null ? <ButtonField variant="green" small name="add" inline rounded onClick={() => this.addPattern(p)}/> : - <ButtonField variant="green" small name="save" inline rounded onClick={() => this.updatePattern(p)}/>} + <ButtonField variant="green" small name="save" inline rounded + onClick={() => this.updatePattern(p)}/>} </td> </tr> : <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> - <td>{p.flags.multi_line ? "yes": "no"}</td> - <td>{p.flags.utf_8_mode ? "yes": "no"}</td> - <td>{p.flags.unicode_property ? "yes": "no"}</td> + <td>{p.flags.caseless ? "yes" : "no"}</td> + <td>{p.flags.dot_all ? "yes" : "no"}</td> + <td>{p.flags.multi_line ? "yes" : "no"}</td> + <td>{p.flags.utf_8_mode ? "yes" : "no"}</td> + <td>{p.flags.unicode_property ? "yes" : "no"}</td> <td>{p.min_occurrences}</td> <td>{p.max_occurrences}</td> <td>{this.directions[p.direction]}</td> {!isUpdate && <td><ButtonField variant="blue" small rounded name="edit" - onClick={() => this.editPattern(p) }/></td>} + onClick={() => this.editPattern(p)}/></td>} </tr> ); @@ -296,9 +302,9 @@ class RulePane extends Component { <div className="section-header"> <span className="api-request">GET /api/rules</span> {this.state.rulesStatusCode && - <span className="api-response"><LinkPopover text={this.state.rulesStatusCode} - content={this.state.rulesResponse} - placement="left" /></span>} + <span className="api-response"><LinkPopover text={this.state.rulesStatusCode} + content={this.state.rulesResponse} + placement="left"/></span>} </div> <div className="section-content"> @@ -327,7 +333,7 @@ class RulePane extends Component { </span> <span className="api-response"><LinkPopover text={this.state.ruleStatusCode} content={this.state.ruleResponse} - placement="left" /></span> + placement="left"/></span> </div> <div className="section-content"> @@ -336,11 +342,11 @@ class RulePane extends Component { <Col> <InputField name="name" inline value={rule.name} onChange={(v) => this.updateParam((r) => r.name = v)} - error={this.state.ruleNameError} /> + error={this.state.ruleNameError}/> <ColorField inline value={rule.color} error={this.state.ruleColorError} - onChange={(v) => this.updateParam((r) => r.color = v)} /> + onChange={(v) => this.updateParam((r) => r.color = v)}/> <TextField name="notes" rows={2} value={rule.notes} - onChange={(v) => this.updateParam((r) => r.notes = v)} /> + onChange={(v) => this.updateParam((r) => r.notes = v)}/> </Col> <Col style={{"paddingTop": "6px"}}> @@ -348,29 +354,29 @@ class RulePane extends Component { <NumericField name="service_port" inline value={rule.filter.service_port} onChange={(v) => this.updateParam((r) => r.filter.service_port = v)} min={0} max={65565} error={this.state.ruleServicePortError} - readonly={isUpdate} /> + readonly={isUpdate}/> <NumericField name="client_port" inline value={rule.filter.client_port} onChange={(v) => this.updateParam((r) => r.filter.client_port = v)} min={0} max={65565} error={this.state.ruleClientPortError} - readonly={isUpdate} /> + readonly={isUpdate}/> <InputField name="client_address" value={rule.filter.client_address} error={this.state.ruleClientAddressError} readonly={isUpdate} - onChange={(v) => this.updateParam((r) => r.filter.client_address = v)} /> + onChange={(v) => this.updateParam((r) => r.filter.client_address = v)}/> </Col> <Col style={{"paddingTop": "11px"}}> <NumericField name="min_duration" inline value={rule.filter.min_duration} error={this.state.ruleDurationError} readonly={isUpdate} - onChange={(v) => this.updateParam((r) => r.filter.min_duration = v)} /> + onChange={(v) => this.updateParam((r) => r.filter.min_duration = v)}/> <NumericField name="max_duration" inline value={rule.filter.max_duration} error={this.state.ruleDurationError} readonly={isUpdate} - onChange={(v) => this.updateParam((r) => r.filter.max_duration = v)} /> + onChange={(v) => this.updateParam((r) => r.filter.max_duration = v)}/> <NumericField name="min_bytes" inline value={rule.filter.min_bytes} error={this.state.ruleBytesError} readonly={isUpdate} - onChange={(v) => this.updateParam((r) => r.filter.min_bytes = v)} /> + onChange={(v) => this.updateParam((r) => r.filter.min_bytes = v)}/> <NumericField name="max_bytes" inline value={rule.filter.max_bytes} error={this.state.ruleBytesError} readonly={isUpdate} - onChange={(v) => this.updateParam((r) => r.filter.max_bytes = v)} /> + onChange={(v) => this.updateParam((r) => r.filter.max_bytes = v)}/> </Col> </Row> </Container> @@ -388,7 +394,7 @@ class RulePane extends Component { <th>min</th> <th>max</th> <th>direction</th> - {!isUpdate && <th>actions</th> } + {!isUpdate && <th>actions</th>} </tr> </thead> <tbody> @@ -403,7 +409,7 @@ class RulePane extends Component { <div className="section-footer"> {<ButtonField variant="red" name="cancel" bordered onClick={this.reset}/>} <ButtonField variant={isUpdate ? "blue" : "green"} name={isUpdate ? "update_rule" : "add_rule"} - bordered onClick={isUpdate ? this.updateRule : this.addRule} /> + bordered onClick={isUpdate ? this.updateRule : this.addRule}/> </div> </div> </div> diff --git a/frontend/src/components/panels/ServicePane.js b/frontend/src/components/panels/ServicePane.js index 0e99652..22c6655 100644 --- a/frontend/src/components/panels/ServicePane.js +++ b/frontend/src/components/panels/ServicePane.js @@ -12,28 +12,13 @@ import ButtonField from "../fields/ButtonField"; import validation from "../../validation"; import LinkPopover from "../objects/LinkPopover"; import {createCurlCommand} from "../../utils"; +import dispatcher from "../../dispatcher"; const classNames = require('classnames'); const _ = require('lodash'); class ServicePane extends Component { - constructor(props) { - super(props); - - this.state = { - services: [], - currentService: this.emptyService, - }; - - document.title = "caronte:~/services$"; - } - - componentDidMount() { - this.reset(); - this.loadServices(); - } - emptyService = { "port": 0, "name": "", @@ -41,6 +26,24 @@ class ServicePane extends Component { "notes": "" }; + state = { + services: [], + currentService: this.emptyService, + }; + + componentDidMount() { + this.reset(); + this.loadServices(); + + dispatcher.register("notifications", payload => { + if (payload.event === "services.edit") { + this.loadServices(); + } + }); + + document.title = "caronte:~/services$"; + } + loadServices = () => { backend.get("/api/services") .then(res => this.setState({services: Object.values(res.json), servicesStatusCode: res.status})) diff --git a/frontend/src/components/panels/common.scss b/frontend/src/components/panels/common.scss index 121a917..1468f35 100644 --- a/frontend/src/components/panels/common.scss +++ b/frontend/src/components/panels/common.scss @@ -2,11 +2,9 @@ .pane-container { height: 100%; - padding: 10px 10px 0; background-color: $color-primary-3; .pane-section { - margin-bottom: 10px; background-color: $color-primary-0; .section-header { @@ -14,7 +12,7 @@ font-weight: 500; display: flex; padding: 5px 10px; - background-color: $color-primary-2; + background-color: $color-primary-3; .api-request { flex: 1; |