diff options
Diffstat (limited to 'frontend/src/components/panels')
-rw-r--r-- | frontend/src/components/panels/ConnectionsPane.js | 107 | ||||
-rw-r--r-- | frontend/src/components/panels/PcapsPane.js | 40 | ||||
-rw-r--r-- | frontend/src/components/panels/RulesPane.js | 51 | ||||
-rw-r--r-- | frontend/src/components/panels/SearchPane.js | 34 | ||||
-rw-r--r-- | frontend/src/components/panels/ServicesPane.js | 68 |
5 files changed, 169 insertions, 131 deletions
diff --git a/frontend/src/components/panels/ConnectionsPane.js b/frontend/src/components/panels/ConnectionsPane.js index ea47059..23c6114 100644 --- a/frontend/src/components/panels/ConnectionsPane.js +++ b/frontend/src/components/panels/ConnectionsPane.js @@ -15,20 +15,20 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -import React, {Component} from 'react'; -import './ConnectionsPane.scss'; -import Connection from "../objects/Connection"; -import Table from 'react-bootstrap/Table'; +import React, {Component} from "react"; +import Table from "react-bootstrap/Table"; +import {Redirect} from "react-router"; import {withRouter} from "react-router-dom"; import backend from "../../backend"; -import ConnectionMatchedRules from "../objects/ConnectionMatchedRules"; -import log from "../../log"; -import ButtonField from "../fields/ButtonField"; import dispatcher from "../../dispatcher"; -import {Redirect} from "react-router"; +import log from "../../log"; import {updateParams} from "../../utils"; +import ButtonField from "../fields/ButtonField"; +import Connection from "../objects/Connection"; +import ConnectionMatchedRules from "../objects/ConnectionMatchedRules"; +import "./ConnectionsPane.scss"; -const classNames = require('classnames'); +const classNames = require("classnames"); class ConnectionsPane extends Component { @@ -67,55 +67,56 @@ class ConnectionsPane extends Component { this.loadConnections(additionalParams, urlParams, true).then(() => log.debug("Connections loaded")); - this.connectionsFiltersCallback = payload => { - const newParams = updateParams(this.state.urlParams, payload); - if (this.state.urlParams.toString() === newParams.toString()) { - return; - } - - log.debug("Update following url params:", payload); - this.queryStringRedirect = true; - this.setState({urlParams: newParams}); - - this.loadConnections({limit: this.queryLimit}, newParams) - .then(() => log.info("ConnectionsPane reloaded after query string update")); - }; - dispatcher.register("connections_filters", this.connectionsFiltersCallback); - - this.timelineUpdatesCallback = payload => { - this.connectionsListRef.current.scrollTop = 0; - this.loadConnections({ - "started_after": Math.round(payload.from.getTime() / 1000), - "started_before": Math.round(payload.to.getTime() / 1000), - limit: this.maxConnections - }).then(() => log.info(`Loading connections between ${payload.from} and ${payload.to}`)); - }; - dispatcher.register("timeline_updates", this.timelineUpdatesCallback); - - this.notificationsCallback = payload => { - if (payload.event === "rules.new" || payload.event === "rules.edit") { - this.loadRules().then(() => log.debug("Loaded connection rules after notification update")); - } - if (payload.event === "services.edit") { - this.loadServices().then(() => log.debug("Services reloaded after notification update")); - } - }; - dispatcher.register("notifications", this.notificationsCallback); - - this.pulseConnectionsViewCallback = payload => { - this.setState({pulseConnectionsView: true}); - setTimeout(() => this.setState({pulseConnectionsView: false}), payload.duration); - }; - dispatcher.register("pulse_connections_view", this.pulseConnectionsViewCallback); + dispatcher.register("connections_filters", this.handleConnectionsFilters); + dispatcher.register("timeline_updates", this.handleTimelineUpdates); + dispatcher.register("notifications", this.handleNotifications); + dispatcher.register("pulse_connections_view", this.handlePulseConnectionsView); } componentWillUnmount() { - dispatcher.unregister(this.timelineUpdatesCallback); - dispatcher.unregister(this.notificationsCallback); - dispatcher.unregister(this.pulseConnectionsViewCallback); - dispatcher.unregister(this.connectionsFiltersCallback); + dispatcher.unregister(this.handleConnectionsFilters); + dispatcher.unregister(this.handleTimelineUpdates); + dispatcher.unregister(this.handleNotifications); + dispatcher.unregister(this.handlePulseConnectionsView); } + handleConnectionsFilters = (payload) => { + const newParams = updateParams(this.state.urlParams, payload); + if (this.state.urlParams.toString() === newParams.toString()) { + return; + } + + log.debug("Update following url params:", payload); + this.queryStringRedirect = true; + this.setState({urlParams: newParams}); + + this.loadConnections({limit: this.queryLimit}, newParams) + .then(() => log.info("ConnectionsPane reloaded after query string update")); + }; + + handleTimelineUpdates = (payload) => { + this.connectionsListRef.current.scrollTop = 0; + this.loadConnections({ + "started_after": Math.round(payload.from.getTime() / 1000), + "started_before": Math.round(payload.to.getTime() / 1000), + limit: this.maxConnections + }).then(() => log.info(`Loading connections between ${payload.from} and ${payload.to}`)); + }; + + handleNotifications = (payload) => { + if (payload.event === "rules.new" || payload.event === "rules.edit") { + this.loadRules().then(() => log.debug("Loaded connection rules after notification update")); + } + if (payload.event === "services.edit") { + this.loadServices().then(() => log.debug("Services reloaded after notification update")); + } + }; + + handlePulseConnectionsView = (payload) => { + this.setState({pulseConnectionsView: true}); + setTimeout(() => this.setState({pulseConnectionsView: false}), payload.duration); + }; + connectionSelected = (c) => { this.connectionSelectedRedirect = true; this.setState({selected: c.id}); diff --git a/frontend/src/components/panels/PcapsPane.js b/frontend/src/components/panels/PcapsPane.js index fd3db75..64e7804 100644 --- a/frontend/src/components/panels/PcapsPane.js +++ b/frontend/src/components/panels/PcapsPane.js @@ -24,6 +24,7 @@ import ButtonField from "../fields/ButtonField"; import CheckField from "../fields/CheckField"; import InputField from "../fields/InputField"; import TextField from "../fields/TextField"; +import CopyLinkPopover from "../objects/CopyLinkPopover"; import LinkPopover from "../objects/LinkPopover"; import "./common.scss"; import "./PcapsPane.scss"; @@ -44,16 +45,20 @@ class PcapsPane extends Component { componentDidMount() { this.loadSessions(); - - dispatcher.register("notifications", (payload) => { - if (payload.event === "pcap.upload" || payload.event === "pcap.file") { - this.loadSessions(); - } - }); - + dispatcher.register("notifications", this.handleNotifications); document.title = "caronte:~/pcaps$"; } + componentWillUnmount() { + dispatcher.unregister(this.handleNotifications); + } + + handleNotifications = (payload) => { + if (payload.event.startsWith("pcap")) { + this.loadSessions(); + } + }; + loadSessions = () => { backend.get("/api/pcap/sessions") .then((res) => this.setState({sessions: res.json, sessionsStatusCode: res.status})) @@ -130,10 +135,19 @@ class PcapsPane extends Component { }; render() { - let sessions = this.state.sessions.map((s) => - <tr key={s.id} className="row-small row-clickable"> - <td>{s["id"].substring(0, 8)}</td> - <td>{dateTimeToTime(s["started_at"])}</td> + let sessions = this.state.sessions.map((s) => { + const startedAt = new Date(s["started_at"]); + const completedAt = new Date(s["completed_at"]); + let timeInfo = <div> + <span>Started at {startedAt.toLocaleDateString() + " " + startedAt.toLocaleTimeString()}</span><br/> + <span>Completed at {completedAt.toLocaleDateString() + " " + completedAt.toLocaleTimeString()}</span> + </div>; + + return <tr key={s.id} className="row-small row-clickable"> + <td><CopyLinkPopover text={s["id"].substring(0, 8)} value={s["id"]}/></td> + <td> + <LinkPopover text={dateTimeToTime(s["started_at"])} content={timeInfo} placement="right"/> + </td> <td>{durationBetween(s["started_at"], s["completed_at"])}</td> <td>{formatSize(s["size"])}</td> <td>{s["processed_packets"]}</td> @@ -143,8 +157,8 @@ class PcapsPane extends Component { placement="left"/></td> <td className="table-cell-action"><a href={"/api/pcap/sessions/" + s["id"] + "/download"}>download</a> </td> - </tr> - ); + </tr>; + }); const handleUploadFileChange = (file) => { this.setState({ diff --git a/frontend/src/components/panels/RulesPane.js b/frontend/src/components/panels/RulesPane.js index a66cde7..d872b47 100644 --- a/frontend/src/components/panels/RulesPane.js +++ b/frontend/src/components/panels/RulesPane.js @@ -15,26 +15,26 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -import React, {Component} from 'react'; -import './common.scss'; -import './RulesPane.scss'; -import Table from "react-bootstrap/Table"; +import React, {Component} from "react"; import {Col, Container, Row} from "react-bootstrap"; -import InputField from "../fields/InputField"; -import CheckField from "../fields/CheckField"; -import TextField from "../fields/TextField"; +import Table from "react-bootstrap/Table"; import backend from "../../backend"; -import NumericField from "../fields/extensions/NumericField"; -import ColorField from "../fields/extensions/ColorField"; -import ChoiceField from "../fields/ChoiceField"; -import ButtonField from "../fields/ButtonField"; +import dispatcher from "../../dispatcher"; import validation from "../../validation"; +import ButtonField from "../fields/ButtonField"; +import CheckField from "../fields/CheckField"; +import ChoiceField from "../fields/ChoiceField"; +import ColorField from "../fields/extensions/ColorField"; +import NumericField from "../fields/extensions/NumericField"; +import InputField from "../fields/InputField"; +import TextField from "../fields/TextField"; +import CopyLinkPopover from "../objects/CopyLinkPopover"; import LinkPopover from "../objects/LinkPopover"; -import {randomClassName} from "../../utils"; -import dispatcher from "../../dispatcher"; +import "./common.scss"; +import "./RulesPane.scss"; -const classNames = require('classnames'); -const _ = require('lodash'); +const classNames = require("classnames"); +const _ = require("lodash"); class RulesPane extends Component { @@ -88,15 +88,20 @@ class RulesPane extends Component { this.reset(); this.loadRules(); - dispatcher.register("notifications", payload => { - if (payload.event === "rules.new" || payload.event === "rules.edit") { - this.loadRules(); - } - }); - + dispatcher.register("notifications", this.handleNotifications); document.title = "caronte:~/rules$"; } + componentWillUnmount() { + dispatcher.unregister(this.handleNotifications); + } + + handleNotifications = (payload) => { + if (payload.event === "rules.new" || payload.event === "rules.edit") { + this.loadRules(); + } + }; + loadRules = () => { backend.get("/api/rules").then(res => this.setState({rules: res.json, rulesStatusCode: res.status})) .catch(res => this.setState({rulesStatusCode: res.status, rulesResponse: JSON.stringify(res.json)})); @@ -249,7 +254,7 @@ class RulesPane extends Component { this.reset(); this.setState({selectedRule: _.cloneDeep(r)}); }} className={classNames("row-small", "row-clickable", {"row-selected": rule.id === r.id})}> - <td>{r["id"].substring(0, 8)}</td> + <CopyLinkPopover text={r["id"].substring(0, 8)} value={r["id"]}/> <td>{r["name"]}</td> <td><ButtonField name={r["color"]} color={r["color"]} small/></td> <td>{r["notes"]}</td> @@ -260,7 +265,7 @@ class RulesPane extends Component { rule.patterns.concat(this.state.newPattern) : rule.patterns ).map(p => p === pattern ? - <tr key={randomClassName()}> + <tr key={"new_pattern"}> <td style={{"width": "500px"}}> <InputField small active={this.state.patternRegexFocused} value={pattern.regex} onChange={(v) => { diff --git a/frontend/src/components/panels/SearchPane.js b/frontend/src/components/panels/SearchPane.js index d3c0c8b..d36e85e 100644 --- a/frontend/src/components/panels/SearchPane.js +++ b/frontend/src/components/panels/SearchPane.js @@ -60,15 +60,14 @@ class SearchPane extends Component { this.reset(); this.loadSearches(); - dispatcher.register("notifications", payload => { - if (payload.event === "searches.new") { - this.loadSearches(); - } - }); - + dispatcher.register("notifications", this.handleNotification); document.title = "caronte:~/searches$"; } + componentWillUnmount() { + dispatcher.unregister(this.handleNotification); + } + loadSearches = () => { backend.get("/api/searches") .then(res => this.setState({searches: res.json, searchesStatusCode: res.status})) @@ -77,14 +76,18 @@ class SearchPane extends Component { performSearch = () => { const options = this.state.currentSearchOptions; + this.setState({loading: true}); if (this.validateSearch(options)) { backend.post("/api/searches/perform", options).then(res => { this.reset(); - this.setState({searchStatusCode: res.status}); + this.setState({searchStatusCode: res.status, loading: false}); this.loadSearches(); this.viewSearch(res.json.id); }).catch(res => { - this.setState({searchStatusCode: res.status, searchResponse: JSON.stringify(res.json)}); + this.setState({ + searchStatusCode: res.status, searchResponse: JSON.stringify(res.json), + loading: false + }); }); } }; @@ -156,6 +159,12 @@ class SearchPane extends Component { dispatcher.dispatch("connections_filters", {"performed_search": searchId}); }; + handleNotification = (payload) => { + if (payload.event === "searches.new") { + this.loadSearches(); + } + }; + render() { const options = this.state.currentSearchOptions; @@ -263,7 +272,8 @@ class SearchPane extends Component { onChange={v => this.updateParam(s => s["regex_search"]["not_pattern"] = v)}/> <div className="checkbox-line"> - <CheckField checked={options["regex_search"]["case_insensitive"]} name="case_insensitive" + <CheckField checked={options["regex_search"]["case_insensitive"]} + name="case_insensitive" readonly={textOptionsModified} small onChange={(v) => this.updateParam(s => s["regex_search"]["case_insensitive"] = v)}/> <CheckField checked={options["regex_search"]["multi_line"]} name="multi_line" @@ -284,8 +294,10 @@ class SearchPane extends Component { </div> <div className="section-footer"> - <ButtonField variant="red" name="cancel" bordered onClick={this.reset}/> - <ButtonField variant="green" name="perform_search" bordered onClick={this.performSearch}/> + <ButtonField variant="red" name="cancel" bordered disabled={this.state.loading} + onClick={this.reset}/> + <ButtonField variant="green" name="perform_search" bordered + disabled={this.state.loading} onClick={this.performSearch}/> </div> </div> </div> diff --git a/frontend/src/components/panels/ServicesPane.js b/frontend/src/components/panels/ServicesPane.js index bc82356..48d9e29 100644 --- a/frontend/src/components/panels/ServicesPane.js +++ b/frontend/src/components/panels/ServicesPane.js @@ -15,24 +15,24 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -import React, {Component} from 'react'; -import './common.scss'; -import './ServicesPane.scss'; -import Table from "react-bootstrap/Table"; +import React, {Component} from "react"; import {Col, Container, Row} from "react-bootstrap"; -import InputField from "../fields/InputField"; -import TextField from "../fields/TextField"; +import Table from "react-bootstrap/Table"; import backend from "../../backend"; -import NumericField from "../fields/extensions/NumericField"; -import ColorField from "../fields/extensions/ColorField"; -import ButtonField from "../fields/ButtonField"; +import dispatcher from "../../dispatcher"; +import {createCurlCommand} from "../../utils"; import validation from "../../validation"; +import ButtonField from "../fields/ButtonField"; +import ColorField from "../fields/extensions/ColorField"; +import NumericField from "../fields/extensions/NumericField"; +import InputField from "../fields/InputField"; +import TextField from "../fields/TextField"; import LinkPopover from "../objects/LinkPopover"; -import {createCurlCommand} from "../../utils"; -import dispatcher from "../../dispatcher"; +import "./common.scss"; +import "./ServicesPane.scss"; -const classNames = require('classnames'); -const _ = require('lodash'); +const classNames = require("classnames"); +const _ = require("lodash"); class ServicesPane extends Component { @@ -52,15 +52,20 @@ class ServicesPane extends Component { this.reset(); this.loadServices(); - dispatcher.register("notifications", payload => { - if (payload.event === "services.edit") { - this.loadServices(); - } - }); - + dispatcher.register("notifications", this.handleNotifications); document.title = "caronte:~/services$"; } + componentWillUnmount() { + dispatcher.unregister(this.handleNotifications); + } + + handleNotifications = (payload) => { + if (payload.event === "services.edit") { + this.loadServices(); + } + }; + loadServices = () => { backend.get("/api/services") .then(res => this.setState({services: Object.values(res.json), servicesStatusCode: res.status})) @@ -125,10 +130,10 @@ class ServicesPane extends Component { <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 })}> + }} className={classNames("row-small", "row-clickable", {"row-selected": service.port === s.port})}> <td>{s["port"]}</td> <td>{s["name"]}</td> - <td><ButtonField name={s["color"]} color={s["color"]} small /></td> + <td><ButtonField name={s["color"]} color={s["color"]} small/></td> <td>{s["notes"]}</td> </tr> ); @@ -141,9 +146,9 @@ class ServicesPane extends Component { <div className="section-header"> <span className="api-request">GET /api/services</span> {this.state.servicesStatusCode && - <span className="api-response"><LinkPopover text={this.state.servicesStatusCode} - content={this.state.servicesResponse} - placement="left" /></span>} + <span className="api-response"><LinkPopover text={this.state.servicesStatusCode} + content={this.state.servicesResponse} + placement="left"/></span>} </div> <div className="section-content"> @@ -170,7 +175,7 @@ class ServicesPane extends Component { <span className="api-request">PUT /api/services</span> <span className="api-response"><LinkPopover text={this.state.serviceStatusCode} content={this.state.serviceResponse} - placement="left" /></span> + placement="left"/></span> </div> <div className="section-content"> @@ -179,17 +184,17 @@ class ServicesPane extends Component { <Col> <NumericField name="port" value={service.port} onChange={(v) => this.updateParam((s) => s.port = v)} - min={0} max={65565} error={this.state.servicePortError} /> + min={0} max={65565} error={this.state.servicePortError}/> <InputField name="name" value={service.name} onChange={(v) => this.updateParam((s) => s.name = v)} - error={this.state.serviceNameError} /> + error={this.state.serviceNameError}/> <ColorField value={service.color} error={this.state.serviceColorError} - onChange={(v) => this.updateParam((s) => s.color = v)} /> + onChange={(v) => this.updateParam((s) => s.color = v)}/> </Col> <Col> <TextField name="notes" rows={7} value={service.notes} - onChange={(v) => this.updateParam((s) => s.notes = v)} /> + onChange={(v) => this.updateParam((s) => s.notes = v)}/> </Col> </Row> </Container> @@ -199,8 +204,9 @@ class ServicesPane extends Component { <div className="section-footer"> {<ButtonField variant="red" name="cancel" bordered onClick={this.reset}/>} - <ButtonField variant={isUpdate ? "blue" : "green"} name={isUpdate ? "update_service" : "add_service"} - bordered onClick={this.updateService} /> + <ButtonField variant={isUpdate ? "blue" : "green"} + name={isUpdate ? "update_service" : "add_service"} + bordered onClick={this.updateService}/> </div> </div> </div> |