From c5434cccee8661de3082c0c777375eb8e6f76865 Mon Sep 17 00:00:00 2001 From: JJ Date: Thu, 18 Jul 2024 17:38:57 -0700 Subject: remove unnecessary jsx stuff --- frontend/src/components/panels/ConnectionsPane.jsx | 310 -------------- frontend/src/components/panels/MainPane.jsx | 112 ----- frontend/src/components/panels/PcapsPane.jsx | 287 ------------- frontend/src/components/panels/RulesPane.jsx | 469 --------------------- frontend/src/components/panels/SearchPane.jsx | 309 -------------- frontend/src/components/panels/ServicesPane.jsx | 233 ---------- frontend/src/components/panels/StatsPane.jsx | 274 ------------ frontend/src/components/panels/StreamsPane.jsx | 453 -------------------- 8 files changed, 2447 deletions(-) delete mode 100644 frontend/src/components/panels/ConnectionsPane.jsx delete mode 100644 frontend/src/components/panels/MainPane.jsx delete mode 100644 frontend/src/components/panels/PcapsPane.jsx delete mode 100644 frontend/src/components/panels/RulesPane.jsx delete mode 100644 frontend/src/components/panels/SearchPane.jsx delete mode 100644 frontend/src/components/panels/ServicesPane.jsx delete mode 100644 frontend/src/components/panels/StatsPane.jsx delete mode 100644 frontend/src/components/panels/StreamsPane.jsx (limited to 'frontend/src/components/panels') diff --git a/frontend/src/components/panels/ConnectionsPane.jsx b/frontend/src/components/panels/ConnectionsPane.jsx deleted file mode 100644 index 2c7fadd..0000000 --- a/frontend/src/components/panels/ConnectionsPane.jsx +++ /dev/null @@ -1,310 +0,0 @@ -/* - * This file is part of caronte (https://github.com/eciavatta/caronte). - * Copyright (c) 2020 Emiliano Ciavatta. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -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 dispatcher from "../../dispatcher"; -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"; - -import classNames from 'classnames'; - -class ConnectionsPane extends Component { - - state = { - loading: false, - connections: [], - firstConnection: null, - lastConnection: null, - }; - - constructor(props) { - super(props); - - this.scrollTopThreashold = 0.00001; - this.scrollBottomThreashold = 0.99999; - this.maxConnections = 200; - this.queryLimit = 50; - this.connectionsListRef = React.createRef(); - this.lastScrollPosition = 0; - } - - componentDidMount() { - let urlParams = new URLSearchParams(this.props.location.search); - this.setState({urlParams}); - - const additionalParams = {limit: this.queryLimit}; - - const match = this.props.location.pathname.match(/^\/connections\/([a-f0-9]{24})$/); - if (match != null) { - const id = match[1]; - additionalParams.from = id; - backend.get(`/api/connections/${id}`) - .then((res) => this.connectionSelected(res.json)) - .catch((error) => log.error("Error loading initial connection", error)); - } - - this.loadConnections(additionalParams, urlParams, true).then(() => log.debug("Connections loaded")); - - 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.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}); - this.props.onSelected(c); - log.debug(`Connection ${c.id} selected`); - }; - - handleScroll = (e) => { - if (this.disableScrollHandler) { - this.lastScrollPosition = e.currentTarget.scrollTop; - return; - } - - let relativeScroll = e.currentTarget.scrollTop / (e.currentTarget.scrollHeight - e.currentTarget.clientHeight); - if (!this.state.loading && relativeScroll > this.scrollBottomThreashold) { - this.loadConnections({from: this.state.lastConnection.id, limit: this.queryLimit,}) - .then(() => log.info("Following connections loaded")); - } - if (!this.state.loading && relativeScroll < this.scrollTopThreashold) { - this.loadConnections({to: this.state.firstConnection.id, limit: this.queryLimit,}) - .then(() => log.info("Previous connections loaded")); - if (this.state.showMoreRecentButton) { - this.setState({showMoreRecentButton: false}); - } - } else { - if (this.lastScrollPosition > e.currentTarget.scrollTop) { - if (!this.state.showMoreRecentButton) { - this.setState({showMoreRecentButton: true}); - } - } else { - if (this.state.showMoreRecentButton) { - this.setState({showMoreRecentButton: false}); - } - } - } - this.lastScrollPosition = e.currentTarget.scrollTop; - }; - - async loadConnections(additionalParams, initialParams = null, isInitial = false) { - if (!initialParams) { - initialParams = this.state.urlParams; - } - const urlParams = new URLSearchParams(initialParams.toString()); - for (const [name, value] of Object.entries(additionalParams)) { - urlParams.set(name, value); - } - - this.setState({loading: true}); - if (!this.state.rules) { - await this.loadRules(); - } - if (!this.state.services) { - await this.loadServices(); - } - - let res = (await backend.get(`/api/connections?${urlParams}`)).json; - - let connections = this.state.connections; - let firstConnection = this.state.firstConnection; - let lastConnection = this.state.lastConnection; - - if (additionalParams && additionalParams.from && !additionalParams.to) { - if (res.length > 0) { - if (!isInitial) { - res = res.slice(1); - } - connections = this.state.connections.concat(res); - lastConnection = connections[connections.length - 1]; - if (isInitial) { - firstConnection = connections[0]; - } - if (connections.length > this.maxConnections) { - connections = connections.slice(connections.length - this.maxConnections, - connections.length - 1); - firstConnection = connections[0]; - } - } - } else if (additionalParams && additionalParams.to && !additionalParams.from) { - if (res.length > 0) { - connections = res.slice(0, res.length - 1).concat(this.state.connections); - firstConnection = connections[0]; - if (connections.length > this.maxConnections) { - connections = connections.slice(0, this.maxConnections); - lastConnection = connections[this.maxConnections - 1]; - } - } - } else { - if (res.length > 0) { - connections = res; - firstConnection = connections[0]; - lastConnection = connections[connections.length - 1]; - } else { - connections = []; - firstConnection = null; - lastConnection = null; - } - } - - this.setState({loading: false, connections, firstConnection, lastConnection}); - - if (firstConnection != null && lastConnection != null) { - dispatcher.dispatch("connection_updates", { - from: new Date(lastConnection["started_at"]), - to: new Date(firstConnection["started_at"]) - }); - } - } - - loadRules = async () => { - return backend.get("/api/rules").then((res) => this.setState({rules: res.json})); - }; - - loadServices = async () => { - return backend.get("/api/services").then((res) => this.setState({services: res.json})); - }; - - render() { - let redirect; - if (this.connectionSelectedRedirect) { - redirect = ; - this.connectionSelectedRedirect = false; - } else if (this.queryStringRedirect) { - redirect = ; - this.queryStringRedirect = false; - } - - let loading = null; - if (this.state.loading) { - loading = - Loading... - ; - } - - return ( -
- {this.state.showMoreRecentButton &&
- { - this.disableScrollHandler = true; - this.connectionsListRef.current.scrollTop = 0; - this.loadConnections({limit: this.queryLimit}) - .then(() => { - this.disableScrollHandler = false; - log.info("Most recent connections loaded"); - }); - }}/> -
} - -
- - - - - - - - - - - - - - - - - { - this.state.connections.flatMap((c) => { - return [ this.connectionSelected(c)} - selected={this.state.selected === c.id} - onMarked={(marked) => c.marked = marked} - onCommented={(comment) => c.comment = comment} - services={this.state.services}/>, - c.matched_rules.length > 0 && - - ]; - }) - } - {loading} - -
servicesrcipsrcportdstipdstportstarted_atdurationupdownactions
- - {redirect} -
-
- ); - } - -} - -export default withRouter(ConnectionsPane); diff --git a/frontend/src/components/panels/MainPane.jsx b/frontend/src/components/panels/MainPane.jsx deleted file mode 100644 index ce72be5..0000000 --- a/frontend/src/components/panels/MainPane.jsx +++ /dev/null @@ -1,112 +0,0 @@ -/* - * This file is part of caronte (https://github.com/eciavatta/caronte). - * Copyright (c) 2020 Emiliano Ciavatta. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import React, {Component} from "react"; -import Typed from "typed.js"; -import dispatcher from "../../dispatcher"; -import "./common.scss"; -import "./MainPane.scss"; -import PcapsPane from "./PcapsPane"; -import RulesPane from "./RulesPane"; -import ServicesPane from "./ServicesPane"; -import StreamsPane from "./StreamsPane"; - -class MainPane extends Component { - - state = {}; - - componentDidMount() { - const nl = "^600\n^400"; - const options = { - strings: [ - `welcome to caronte!^1000 the current version is ${this.props.version}` + nl + - "caronte is a network analyzer,^300 it is able to read pcaps and extract connections", // 0 - "the left panel lists all connections that have already been closed" + nl + - "scrolling up the list will load the most recent connections,^300 downward the oldest ones", // 1 - "by selecting a connection you can view its content,^300 which will be shown in the right panel" + nl + - "you can choose the display format,^300 or decide to download the connection content", // 2 - "below there is the timeline,^300 which shows the number of connections per minute per service" + nl + - "you can use the sliding window to move the time range of the connections to be displayed", // 3 - "there are also additional metrics,^300 selectable from the drop-down menu", // 4 - "at the top are the filters,^300 which can be used to select only certain types of connections" + nl + - "you can choose which filters to display in the top bar from the filters window", // 5 - "in the pcaps panel it is possible to analyze new pcaps,^300 or to see the pcaps already analyzed" + nl + - "you can load pcaps from your browser,^300 or process pcaps already present on the filesystem", // 6 - "in the rules panel you can see the rules already created,^300 or create new ones" + nl + - "the rules inserted will be used only to label new connections, not those already analyzed" + nl + - "a connection is tagged if it meets all the requirements specified by the rule", // 7 - "in the services panel you can assign new services or edit existing ones" + nl + - "each service is associated with a port number,^300 and will be shown in the connection list", // 8 - "from the configuration panel you can change the settings of the frontend application", // 9 - "that's all! and have fun!" + nl + "created by @eciavatta" // 10 - ], - typeSpeed: 40, - cursorChar: "_", - backSpeed: 5, - smartBackspace: false, - backDelay: 1500, - preStringTyped: (arrayPos) => { - switch (arrayPos) { - case 1: - return dispatcher.dispatch("pulse_connections_view", {duration: 12000}); - case 2: - return this.setState({backgroundPane: }); - case 3: - this.setState({backgroundPane: null}); - return dispatcher.dispatch("pulse_timeline", {duration: 12000}); - case 6: - return this.setState({backgroundPane: }); - case 7: - return this.setState({backgroundPane: }); - case 8: - return this.setState({backgroundPane: }); - case 10: - return this.setState({backgroundPane: null}); - default: - return; - } - }, - }; - this.typed = new Typed(this.el, options); - } - - componentWillUnmount() { - this.typed.destroy(); - } - - render() { - return ( -
-
-
-
- { - this.el = el; - }}/> -
-
-
-
- {this.state.backgroundPane} -
-
- ); - } - -} - -export default MainPane; diff --git a/frontend/src/components/panels/PcapsPane.jsx b/frontend/src/components/panels/PcapsPane.jsx deleted file mode 100644 index b7d5ce9..0000000 --- a/frontend/src/components/panels/PcapsPane.jsx +++ /dev/null @@ -1,287 +0,0 @@ -/* - * This file is part of caronte (https://github.com/eciavatta/caronte). - * Copyright (c) 2020 Emiliano Ciavatta. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import React, {Component} from "react"; -import Table from "react-bootstrap/Table"; -import backend from "../../backend"; -import dispatcher from "../../dispatcher"; -import {createCurlCommand, dateTimeToTime, durationBetween, formatSize} from "../../utils"; -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"; - -class PcapsPane extends Component { - - state = { - sessions: [], - isUploadFileValid: true, - isUploadFileFocused: false, - uploadFlushAll: false, - isFileValid: true, - isFileFocused: false, - fileValue: "", - processFlushAll: false, - deleteOriginalFile: false - }; - - componentDidMount() { - 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})) - .catch((res) => this.setState({ - sessions: res.json, sessionsStatusCode: res.status, - sessionsResponse: JSON.stringify(res.json) - })); - }; - - uploadPcap = () => { - if (this.state.uploadSelectedFile == null || !this.state.isUploadFileValid) { - this.setState({isUploadFileFocused: true}); - return; - } - - const formData = new FormData(); - formData.append("file", this.state.uploadSelectedFile); - formData.append("flush_all", this.state.uploadFlushAll); - backend.postFile("/api/pcap/upload", formData).then((res) => { - this.setState({ - uploadStatusCode: res.status, - uploadResponse: JSON.stringify(res.json) - }); - this.resetUpload(); - this.loadSessions(); - }).catch((res) => this.setState({ - uploadStatusCode: res.status, - uploadResponse: JSON.stringify(res.json) - }) - ); - }; - - processPcap = () => { - if (this.state.fileValue === "" || !this.state.isFileValid) { - this.setState({isFileFocused: true}); - return; - } - - backend.post("/api/pcap/file", { - "file": this.state.fileValue, - "flush_all": this.state.processFlushAll, - "delete_original_file": this.state.deleteOriginalFile - }).then((res) => { - this.setState({ - processStatusCode: res.status, - processResponse: JSON.stringify(res.json) - }); - this.resetProcess(); - this.loadSessions(); - }).catch((res) => this.setState({ - processStatusCode: res.status, - processResponse: JSON.stringify(res.json) - }) - ); - }; - - resetUpload = () => { - this.setState({ - isUploadFileValid: true, - isUploadFileFocused: false, - uploadFlushAll: false, - uploadSelectedFile: null - }); - }; - - resetProcess = () => { - this.setState({ - isFileValid: true, - isFileFocused: false, - fileValue: "", - processFlushAll: false, - deleteOriginalFile: false, - }); - }; - - render() { - let sessions = this.state.sessions.map((s) => { - const startedAt = new Date(s["started_at"]); - const completedAt = new Date(s["completed_at"]); - let timeInfo =
- Started at {startedAt.toLocaleDateString() + " " + startedAt.toLocaleTimeString()}
- Completed at {completedAt.toLocaleDateString() + " " + completedAt.toLocaleTimeString()} -
; - - return - - - - - {durationBetween(s["started_at"], s["completed_at"])} - {formatSize(s["size"])} - {s["processed_packets"]} - {s["invalid_packets"]} - - download - - ; - }); - - const handleUploadFileChange = (file) => { - this.setState({ - isUploadFileValid: file == null || (file.type.endsWith("pcap") || file.type.endsWith("pcapng")), - isUploadFileFocused: false, - uploadSelectedFile: file, - uploadStatusCode: null, - uploadResponse: null - }); - }; - - const handleFileChange = (file) => { - this.setState({ - isFileValid: (file.endsWith("pcap") || file.endsWith("pcapng")), - isFileFocused: false, - fileValue: file, - processStatusCode: null, - processResponse: null - }); - }; - - const uploadCurlCommand = createCurlCommand("/pcap/upload", "POST", null, { - "file": "@" + ((this.state.uploadSelectedFile != null && this.state.isUploadFileValid) ? - this.state.uploadSelectedFile.name : "invalid.pcap"), - "flush_all": this.state.uploadFlushAll - }); - - const fileCurlCommand = createCurlCommand("/pcap/file", "POST", { - "file": this.state.fileValue, - "flush_all": this.state.processFlushAll, - "delete_original_file": this.state.deleteOriginalFile - }); - - return ( -
-
-
- GET /api/pcap/sessions - -
- -
-
- - - - - - - - - - - - - - - {sessions} - -
idstarted_atdurationsizeprocessed_packetsinvalid_packetspackets_per_serviceactions
-
-
-
- -
-
-
- POST /api/pcap/upload - -
- -
- -
-
- options: - this.setState({uploadFlushAll: v})}/> -
- -
- - -
-
- -
-
- POST /api/pcap/file - -
- -
- - -
-
- this.setState({processFlushAll: v})}/> - this.setState({deleteOriginalFile: v})}/> -
- -
- - -
-
-
-
- ); - } -} - -export default PcapsPane; diff --git a/frontend/src/components/panels/RulesPane.jsx b/frontend/src/components/panels/RulesPane.jsx deleted file mode 100644 index 4cb5e41..0000000 --- a/frontend/src/components/panels/RulesPane.jsx +++ /dev/null @@ -1,469 +0,0 @@ -/* - * This file is part of caronte (https://github.com/eciavatta/caronte). - * Copyright (c) 2020 Emiliano Ciavatta. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import React, { Component } from "react"; -import { Col, Container, Row } from "react-bootstrap"; -import Table from "react-bootstrap/Table"; -import backend from "../../backend"; -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 "./common.scss"; -import "./RulesPane.scss"; - -import classNames from 'classnames'; -import _ from 'lodash'; - -class RulesPane extends Component { - - emptyRule = { - "name": "", - "color": "", - "notes": "", - "enabled": true, - "patterns": [], - "filter": { - "service_port": 0, - "client_address": "", - "client_port": 0, - "min_duration": 0, - "max_duration": 0, - "min_bytes": 0, - "max_bytes": 0 - }, - "version": 0 - }; - emptyPattern = { - "regex": "", - "flags": { - "caseless": false, - "dot_all": false, - "multi_line": false, - "utf_8_mode": false, - "unicode_property": false - }, - "min_occurrences": 0, - "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", 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) })); - }; - - addRule = () => { - if (this.validateRule(this.state.newRule)) { - backend.post("/api/rules", this.state.newRule).then((res) => { - this.reset(); - this.setState({ ruleStatusCode: res.status }); - this.loadRules(); - }).catch((res) => { - this.setState({ ruleStatusCode: res.status, ruleResponse: JSON.stringify(res.json) }); - }); - } - }; - - deleteRule = () => { - const rule = this.state.selectedRule; - backend.delete(`/api/rules/${rule.id}`).then((res) => { - this.reset(); - this.setState({ ruleStatusCode: res.status }); - this.loadRules(); - }).catch((res) => { - this.setState({ ruleStatusCode: res.status, ruleResponse: JSON.stringify(res.json) }); - }); - } - - updateRule = () => { - const rule = this.state.selectedRule; - if (this.validateRule(rule)) { - backend.put(`/api/rules/${rule.id}`, rule).then((res) => { - this.reset(); - this.setState({ ruleStatusCode: res.status }); - this.loadRules(); - }).catch((res) => { - this.setState({ ruleStatusCode: res.status, ruleResponse: JSON.stringify(res.json) }); - }); - } - }; - - validateRule = (rule) => { - let valid = true; - if (rule.name.length < 3) { - this.setState({ ruleNameError: "name.length < 3" }); - valid = false; - } - if (!validation.isValidColor(rule.color)) { - this.setState({ ruleColorError: "color is not hexcolor" }); - valid = false; - } - if (!validation.isValidPort(rule.filter["service_port"])) { - this.setState({ ruleServicePortError: "service_port > 65565" }); - valid = false; - } - if (!validation.isValidPort(rule.filter["client_port"])) { - this.setState({ ruleClientPortError: "client_port > 65565" }); - valid = false; - } - if (!validation.isValidAddress(rule.filter["client_address"])) { - this.setState({ ruleClientAddressError: "client_address is not ip_address" }); - valid = false; - } - if (rule.filter["min_duration"] > rule.filter["max_duration"]) { - this.setState({ ruleDurationError: "min_duration > max_dur." }); - valid = false; - } - if (rule.filter["min_bytes"] > rule.filter["max_bytes"]) { - this.setState({ ruleBytesError: "min_bytes > max_bytes" }); - valid = false; - } - if (rule.patterns.length < 1) { - this.setState({ rulePatternsError: "patterns.length < 1" }); - valid = false; - } - - return valid; - }; - - reset = () => { - const newRule = _.cloneDeep(this.emptyRule); - const newPattern = _.cloneDeep(this.emptyPattern); - this.setState({ - selectedRule: null, - newRule, - selectedPattern: null, - newPattern, - patternRegexFocused: false, - patternOccurrencesFocused: false, - ruleNameError: null, - ruleColorError: null, - ruleServicePortError: null, - ruleClientPortError: null, - ruleClientAddressError: null, - ruleDurationError: null, - ruleBytesError: null, - rulePatternsError: null, - ruleStatusCode: null, - rulesStatusCode: null, - ruleResponse: null, - rulesResponse: null - }); - }; - - updateParam = (callback) => { - const updatedRule = this.currentRule(); - callback(updatedRule); - this.setState({ newRule: updatedRule }); - }; - - currentRule = () => this.state.selectedRule != null ? this.state.selectedRule : this.state.newRule; - - addPattern = (pattern) => { - if (!this.validatePattern(pattern)) { - return; - } - - const newPattern = _.cloneDeep(this.emptyPattern); - this.currentRule().patterns.push(pattern); - this.setState({ newPattern }); - }; - - editPattern = (pattern) => { - this.setState({ - selectedPattern: pattern - }); - }; - - updatePattern = (pattern) => { - if (!this.validatePattern(pattern)) { - return; - } - - this.setState({ - selectedPattern: null - }); - }; - - validatePattern = (pattern) => { - let valid = true; - if (pattern.regex === "") { - valid = false; - this.setState({ patternRegexFocused: true }); - } - if (pattern["min_occurrences"] > pattern["max_occurrences"]) { - valid = false; - this.setState({ patternOccurrencesFocused: true }); - } - return valid; - }; - - render() { - const isUpdate = this.state.selectedRule != null; - const rule = this.currentRule(); - const pattern = this.state.selectedPattern || this.state.newPattern; - - let rules = this.state.rules.map((r) => - { - this.reset(); - this.setState({ selectedRule: _.cloneDeep(r) }); - }} className={classNames("row-small", "row-clickable", { "row-selected": rule.id === r.id })}> - - {r["name"]} - - {r["notes"]} - - ); - - let patterns = (this.state.selectedPattern == null && !isUpdate ? - rule.patterns.concat(this.state.newPattern) : - rule.patterns - ).map((p) => p === pattern ? - - - { - this.updateParam(() => pattern.regex = v); - this.setState({ patternRegexFocused: pattern.regex === "" }); - }} /> - - this.updateParam(() => pattern.flags["caseless"] = v)} /> - this.updateParam(() => pattern.flags["dot_all"] = v)} /> - this.updateParam(() => pattern.flags["multi_line"] = v)} /> - this.updateParam(() => pattern.flags["utf_8_mode"] = v)} /> - this.updateParam(() => pattern.flags["unicode_property"] = v)} /> - - this.updateParam(() => pattern["min_occurrences"] = v)} /> - - - this.updateParam(() => pattern["max_occurrences"] = v)} /> - - s", "s->c"]} - value={this.directions[pattern.direction]} - onChange={(v) => this.updateParam(() => pattern.direction = v)} /> - {this.state.selectedPattern == null ? - this.addPattern(p)} /> : - this.updatePattern(p)} />} - - - : - - {p.regex} - {p.flags["caseless"] ? "yes" : "no"} - {p.flags["dot_all"] ? "yes" : "no"} - {p.flags["multi_line"] ? "yes" : "no"} - {p.flags["utf_8_mode"] ? "yes" : "no"} - {p.flags["unicode_property"] ? "yes" : "no"} - {p["min_occurrences"]} - {p["max_occurrences"]} - {this.directions[p.direction]} - - this.editPattern(p)} - /> - - - ); - - return ( -
-
-
- GET /api/rules - {this.state.rulesStatusCode && - } -
- -
-
- - - - - - - - - - - {rules} - -
idnamecolornotes
-
-
-
- -
-
- - {isUpdate ? `PUT /api/rules/${this.state.selectedRule.id}` : "POST /api/rules"} - - -
- -
- - - - this.updateParam((r) => r.name = v)} - error={this.state.ruleNameError} /> - this.updateParam((r) => r.color = v)} /> - this.updateParam((r) => r.notes = v)} /> - - - - filters: - this.updateParam((r) => r.filter["service_port"] = v)} - min={0} - max={65565} - error={this.state.ruleServicePortError} - /> - this.updateParam((r) => r.filter["client_port"] = v)} - min={0} - max={65565} - error={this.state.ruleClientPortError} - /> - this.updateParam((r) => r.filter["client_address"] = v)} /> - - - - this.updateParam((r) => r.filter["min_duration"] = v)} /> - this.updateParam((r) => r.filter["max_duration"] = v)} /> - this.updateParam((r) => r.filter["min_bytes"] = v)} /> - this.updateParam((r) => r.filter["max_bytes"] = v)} /> - - - - -
- - - - - - - - - - - - - {!isUpdate && } - - - - {patterns} - -
regex!Aa.*\n+UTF8Uni_minmaxdirectionactions
- {this.state.rulePatternsError != null && - error: {this.state.rulePatternsError}} -
-
- -
- {} - - -
-
-
- ); - } - -} - -export default RulesPane; diff --git a/frontend/src/components/panels/SearchPane.jsx b/frontend/src/components/panels/SearchPane.jsx deleted file mode 100644 index 6fe9dc7..0000000 --- a/frontend/src/components/panels/SearchPane.jsx +++ /dev/null @@ -1,309 +0,0 @@ -/* - * This file is part of caronte (https://github.com/eciavatta/caronte). - * Copyright (c) 2020 Emiliano Ciavatta. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import React, {Component} from "react"; -import Table from "react-bootstrap/Table"; -import backend from "../../backend"; -import dispatcher from "../../dispatcher"; -import {createCurlCommand, dateTimeToTime, durationBetween} from "../../utils"; -import ButtonField from "../fields/ButtonField"; -import CheckField from "../fields/CheckField"; -import InputField from "../fields/InputField"; -import TagField from "../fields/TagField"; -import TextField from "../fields/TextField"; -import LinkPopover from "../objects/LinkPopover"; -import "./common.scss"; -import "./SearchPane.scss"; - -import _ from 'lodash'; - -class SearchPane extends Component { - - searchOptions = { - "text_search": { - "terms": null, - "excluded_terms": null, - "exact_phrase": "", - "case_sensitive": false - }, - "regex_search": { - "pattern": "", - "not_pattern": "", - "case_insensitive": false, - "multi_line": false, - "ignore_whitespaces": false, - "dot_character": false - }, - "timeout": 10 - }; - - state = { - searches: [], - currentSearchOptions: this.searchOptions, - }; - - componentDidMount() { - this.reset(); - 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})) - .catch((res) => this.setState({searchesStatusCode: res.status, searchesResponse: JSON.stringify(res.json)})); - }; - - 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, loading: false}); - this.loadSearches(); - this.viewSearch(res.json.id); - }).catch((res) => { - this.setState({ - searchStatusCode: res.status, searchResponse: JSON.stringify(res.json), - loading: false - }); - }); - } - }; - - reset = () => { - this.setState({ - currentSearchOptions: _.cloneDeep(this.searchOptions), - exactPhraseError: null, - patternError: null, - notPatternError: null, - searchStatusCode: null, - searchesStatusCode: null, - searchResponse: null, - searchesResponse: null - }); - }; - - validateSearch = (options) => { - let valid = true; - if (options["text_search"]["exact_phrase"] && options["text_search"]["exact_phrase"].length < 3) { - this.setState({exactPhraseError: "text_search.exact_phrase.length < 3"}); - valid = false; - } - if (options["regex_search"].pattern && options["regex_search"].pattern.length < 3) { - this.setState({patternError: "regex_search.pattern.length < 3"}); - valid = false; - } - if (options["regex_search"]["not_pattern"] && options["regex_search"]["not_pattern"].length < 3) { - this.setState({exactPhraseError: "regex_search.not_pattern.length < 3"}); - valid = false; - } - - return valid; - }; - - updateParam = (callback) => { - callback(this.state.currentSearchOptions); - this.setState({currentSearchOptions: this.state.currentSearchOptions}); - }; - - extractPattern = (options) => { - let pattern = ""; - if (_.isEqual(options.regex_search, this.searchOptions.regex_search)) { // is text search - if (options["text_search"]["exact_phrase"]) { - pattern += `"${options["text_search"]["exact_phrase"]}"`; - } else { - pattern += options["text_search"].terms.join(" "); - if (options["text_search"]["excluded_terms"]) { - pattern += " -" + options["text_search"]["excluded_terms"].join(" -"); - } - } - options["text_search"]["case_sensitive"] && (pattern += "/s"); - } else { // is regex search - if (options["regex_search"].pattern) { - pattern += "/" + options["regex_search"].pattern + "/"; - } else { - pattern += "!/" + options["regex_search"]["not_pattern"] + "/"; - } - options["regex_search"]["case_insensitive"] && (pattern += "i"); - options["regex_search"]["multi_line"] && (pattern += "m"); - options["regex_search"]["ignore_whitespaces"] && (pattern += "x"); - options["regex_search"]["dot_character"] && (pattern += "s"); - } - - return pattern; - }; - - viewSearch = (searchId) => { - dispatcher.dispatch("connections_filters", {"performed_search": searchId}); - }; - - handleNotification = (payload) => { - if (payload.event === "searches.new") { - this.loadSearches(); - } - }; - - render() { - const options = this.state.currentSearchOptions; - - let searches = this.state.searches.map((s) => - - {s.id.substring(0, 8)} - {this.extractPattern(s["search_options"])} - {s["affected_connections_count"]} - {dateTimeToTime(s["started_at"])} - {durationBetween(s["started_at"], s["finished_at"])} - this.viewSearch(s.id)}/> - - ); - - const textOptionsModified = !_.isEqual(this.searchOptions.text_search, options.text_search); - const regexOptionsModified = !_.isEqual(this.searchOptions.regex_search, options.regex_search); - - const curlCommand = createCurlCommand("/searches/perform", "POST", options); - - return ( -
-
-
- GET /api/searches - {this.state.searchesStatusCode && - } -
- -
-
- - - - - - - - - - - - - {searches} - -
idpatternoccurrencesstarted_atdurationactions
-
-
-
- -
-
- POST /api/searches/perform - -
- -
- - NOTE: it is recommended to use the rules for recurring themes. Give preference to textual search over that with regex. - - -
-
- { - return {name: t}; - })} - name="terms" min={3} inline allowNew={true} - readonly={regexOptionsModified || options["text_search"]["exact_phrase"]} - onChange={(tags) => this.updateParam((s) => s["text_search"].terms = tags.map((t) => t.name))}/> - { - return {name: t}; - })} - name="excluded_terms" min={3} inline allowNew={true} - readonly={regexOptionsModified || options["text_search"]["exact_phrase"]} - onChange={(tags) => this.updateParam((s) => s["text_search"]["excluded_terms"] = tags.map((t) => t.name))}/> - - or - - this.updateParam((s) => s["text_search"]["exact_phrase"] = v)} - readonly={regexOptionsModified || (Array.isArray(options["text_search"].terms) && options["text_search"].terms.length > 0)}/> - - this.updateParam((s) => s["text_search"]["case_sensitive"] = v)}/> -
- -
- or -
- -
- this.updateParam((s) => s["regex_search"].pattern = v)}/> - or - this.updateParam((s) => s["regex_search"]["not_pattern"] = v)}/> - -
- this.updateParam((s) => s["regex_search"]["case_insensitive"] = v)}/> - this.updateParam((s) => s["regex_search"]["multi_line"] = v)}/> - this.updateParam((s) => s["regex_search"]["ignore_whitespaces"] = v)}/> - this.updateParam((s) => s["regex_search"]["dot_character"] = v)}/> -
-
-
- - -
- -
- - -
-
-
- ); - } - -} - -export default SearchPane; diff --git a/frontend/src/components/panels/ServicesPane.jsx b/frontend/src/components/panels/ServicesPane.jsx deleted file mode 100644 index 296b329..0000000 --- a/frontend/src/components/panels/ServicesPane.jsx +++ /dev/null @@ -1,233 +0,0 @@ -/* - * This file is part of caronte (https://github.com/eciavatta/caronte). - * Copyright (c) 2020 Emiliano Ciavatta. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import React, {Component} from "react"; -import {Col, Container, Row} from "react-bootstrap"; -import Table from "react-bootstrap/Table"; -import backend from "../../backend"; -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 "./common.scss"; -import "./ServicesPane.scss"; - -import classNames from 'classnames'; -import _ from 'lodash'; - -class ServicesPane extends Component { - - emptyService = { - "port": 0, - "name": "", - "color": "", - "notes": "" - }; - - state = { - services: [], - currentService: this.emptyService, - }; - - componentDidMount() { - this.reset(); - 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})) - .catch((res) => this.setState({servicesStatusCode: res.status, servicesResponse: JSON.stringify(res.json)})); - }; - - updateService = () => { - const service = this.state.currentService; - if (this.validateService(service)) { - backend.put("/api/services", service).then((res) => { - this.reset(); - this.setState({serviceStatusCode: res.status}); - this.loadServices(); - }).catch((res) => { - this.setState({serviceStatusCode: res.status, serviceResponse: JSON.stringify(res.json)}); - }); - } - }; - - deleteService = () => { - const service = this.state.currentService; - if (this.validateService(service)) { - backend.delete("/api/services", service).then((res) => { - this.reset(); - this.setState({serviceStatusCode: res.status}); - this.loadServices(); - }).catch((res) => { - this.setState({serviceStatusCode: res.status, serviceResponse: JSON.stringify(res.json)}); - }); - } - }; - - validateService = (service) => { - let valid = true; - if (!validation.isValidPort(service.port, true)) { - this.setState({servicePortError: "port < 0 || port > 65565"}); - valid = false; - } - if (service.name.length < 3) { - this.setState({serviceNameError: "name.length < 3"}); - valid = false; - } - if (!validation.isValidColor(service.color)) { - this.setState({serviceColorError: "color is not hexcolor"}); - valid = false; - } - - return valid; - }; - - reset = () => { - this.setState({ - isUpdate: false, - currentService: _.cloneDeep(this.emptyService), - servicePortError: null, - serviceNameError: null, - serviceColorError: null, - serviceStatusCode: null, - servicesStatusCode: null, - serviceResponse: null, - servicesResponse: null - }); - }; - - updateParam = (callback) => { - callback(this.state.currentService); - this.setState({currentService: this.state.currentService}); - }; - - render() { - const isUpdate = this.state.isUpdate; - const service = this.state.currentService; - - let services = this.state.services.map((s) => - { - this.reset(); - this.setState({isUpdate: true, currentService: _.cloneDeep(s)}); - }} className={classNames("row-small", "row-clickable", {"row-selected": service.port === s.port})}> - {s["port"]} - {s["name"]} - - {s["notes"]} - - ); - - const curlCommand = createCurlCommand("/services", "PUT", service); - - return ( -
-
-
- GET /api/services - {this.state.servicesStatusCode && - } -
- -
-
- - - - - - - - - - - {services} - -
portnamecolornotes
-
-
-
- -
-
- PUT /api/services - -
- -
- - - - this.updateParam((s) => s.port = v)} - min={0} max={65565} error={this.state.servicePortError}/> - this.updateParam((s) => s.name = v)} - error={this.state.serviceNameError}/> - this.updateParam((s) => s.color = v)}/> - - - - this.updateParam((s) => s.notes = v)}/> - - - - - -
- -
- {} - {isUpdate && } - -
-
-
- ); - } - -} - -export default ServicesPane; diff --git a/frontend/src/components/panels/StatsPane.jsx b/frontend/src/components/panels/StatsPane.jsx deleted file mode 100644 index a35ef0c..0000000 --- a/frontend/src/components/panels/StatsPane.jsx +++ /dev/null @@ -1,274 +0,0 @@ -/* - * This file is part of caronte (https://github.com/eciavatta/caronte). - * Copyright (c) 2020 Emiliano Ciavatta. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import React, {Component} from "react"; -import Table from "react-bootstrap/Table"; -import backend from "../../backend"; -import dispatcher from "../../dispatcher"; -import {formatSize} from "../../utils"; -import ButtonField from "../fields/ButtonField"; -import CopyLinkPopover from "../objects/CopyLinkPopover"; -import LinkPopover from "../objects/LinkPopover"; -import "./common.scss"; -import "./StatsPane.scss"; - -class StatsPane extends Component { - - state = { - rules: [] - }; - - componentDidMount() { - this.loadStats(); - this.loadResourcesStats(); - this.loadRules(); - dispatcher.register("notifications", this.handleNotifications); - document.title = "caronte:~/stats$"; - this.intervalToken = setInterval(() => this.loadResourcesStats(), 3000); - } - - componentWillUnmount() { - dispatcher.unregister(this.handleNotifications); - clearInterval(this.intervalToken); - } - - handleNotifications = (payload) => { - if (payload.event.startsWith("pcap")) { - this.loadStats(); - } else if (payload.event.startsWith("rules")) { - this.loadRules(); - } - }; - - loadStats = () => { - backend.get("/api/statistics/totals") - .then((res) => this.setState({stats: res.json, statsStatusCode: res.status})) - .catch((res) => this.setState({ - stats: res.json, statsStatusCode: res.status, - statsResponse: JSON.stringify(res.json) - })); - }; - - loadResourcesStats = () => { - backend.get("/api/resources/system") - .then((res) => this.setState({resourcesStats: res.json, resourcesStatsStatusCode: res.status})) - .catch((res) => this.setState({ - resourcesStats: res.json, resourcesStatsStatusCode: res.status, - resourcesStatsResponse: JSON.stringify(res.json) - })); - }; - - loadRules = () => { - backend.get("/api/rules").then((res) => this.setState({rules: res.json})); - }; - - render() { - const s = this.state.stats; - const rs = this.state.resourcesStats; - - const ports = s && s["connections_per_service"] ? Object.keys(s["connections_per_service"]) : []; - let connections = 0, clientBytes = 0, serverBytes = 0, totalBytes = 0, duration = 0; - let servicesStats = ports.map((port) => { - connections += s["connections_per_service"][port]; - clientBytes += s["client_bytes_per_service"][port]; - serverBytes += s["server_bytes_per_service"][port]; - totalBytes += s["total_bytes_per_service"][port]; - duration += s["duration_per_service"][port]; - - return - {port} - {formatSize(s["connections_per_service"][port])} - {formatSize(s["client_bytes_per_service"][port])}B - {formatSize(s["server_bytes_per_service"][port])}B - {formatSize(s["total_bytes_per_service"][port])}B - {formatSize(s["duration_per_service"][port] / 1000)}s - ; - }); - servicesStats.push( - totals - {formatSize(connections)} - {formatSize(clientBytes)}B - {formatSize(serverBytes)}B - {formatSize(totalBytes)}B - {formatSize(duration / 1000)}s - ); - - const rulesStats = this.state.rules.map((r) => - - - {r["name"]} - - {formatSize(s && s["matched_rules"] && s["matched_rules"][r.id] ? s["matched_rules"][r.id] : 0)} - - ); - - const cpuStats = (rs ? rs["cpu_times"] : []).map((cpu, index) => - - {cpu["cpu"]} - {cpu["user"]} - {cpu["system"]} - {cpu["idle"]} - {cpu["nice"]} - {cpu["iowait"]} - {rs["cpu_percents"][index].toFixed(2)} % - - ); - - return ( -
-
-
- GET /api/statistics/totals - -
- -
-
- - - - - - - - - - - - - {servicesStats} - -
serviceconnectionsclient_bytesserver_bytestotal_bytesduration
-
- -
- - - - - - - - - - - {rulesStats} - -
rule_idrule_namerule_coloroccurrences
-
-
-
- -
-
- GET /api/resources/system - -
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
typetotalusedfreesharedbuff/cacheavailable
mem{rs && formatSize(rs["virtual_memory"]["total"])}{rs && formatSize(rs["virtual_memory"]["used"])}{rs && formatSize(rs["virtual_memory"]["free"])}{rs && formatSize(rs["virtual_memory"]["shared"])}{rs && formatSize(rs["virtual_memory"]["cached"])}{rs && formatSize(rs["virtual_memory"]["available"])}
swap{rs && formatSize(rs["virtual_memory"]["swaptotal"])}{rs && formatSize(rs["virtual_memory"]["swaptotal"])}{rs && formatSize(rs["virtual_memory"]["swapfree"])}---
-
- -
- - - - - - - - - - - - - - {cpuStats} - -
cpuusersystemidleniceiowaitused_percent
-
- -
- - - - - - - - - - - - - - - - - - - - - -
disk_pathfs_typetotalfreeusedused_percent
{rs && rs["disk_usage"]["path"]}{rs && rs["disk_usage"]["fstype"]}{rs && formatSize(rs["disk_usage"]["total"])}{rs && formatSize(rs["disk_usage"]["free"])}{rs && formatSize(rs["disk_usage"]["used"])}{rs && rs["disk_usage"]["usedPercent"].toFixed(2)} %
-
-
-
-
- ); - } - -} - -export default StatsPane; diff --git a/frontend/src/components/panels/StreamsPane.jsx b/frontend/src/components/panels/StreamsPane.jsx deleted file mode 100644 index 9e88f55..0000000 --- a/frontend/src/components/panels/StreamsPane.jsx +++ /dev/null @@ -1,453 +0,0 @@ -/* - * This file is part of caronte (https://github.com/eciavatta/caronte). - * Copyright (c) 2020 Emiliano Ciavatta. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import DOMPurify from "dompurify"; -import React, { Component } from "react"; -import { Row } from "react-bootstrap"; -import ReactJson from "react-json-view"; -import backend from "../../backend"; -import log from "../../log"; -import rules from "../../model/rules"; -import { downloadBlob, getHeaderValue } from "../../utils"; -import ButtonField from "../fields/ButtonField"; -import ChoiceField from "../fields/ChoiceField"; -import CopyDialog from "../dialogs/CopyDialog"; -import "./StreamsPane.scss"; - -import reactStringReplace from "react-string-replace"; -import classNames from "classnames"; - -class StreamsPane extends Component { - state = { - messages: [], - format: "default", - tryParse: true, - }; - - constructor(props) { - super(props); - - this.validFormats = [ - "default", - "hex", - "hexdump", - "base32", - "base64", - "ascii", - "binary", - "decimal", - "octal", - ]; - } - - componentDidMount() { - if ( - this.props.connection && - this.state.currentId !== this.props.connection.id - ) { - this.setState({ currentId: this.props.connection.id }); - this.loadStream(this.props.connection.id); - } - - document.title = "caronte:~/$"; - } - - componentDidUpdate(prevProps, prevState, snapshot) { - if ( - this.props.connection && - (this.props.connection !== prevProps.connection || - this.state.format !== prevState.format) - ) { - this.closeRenderWindow(); - this.loadStream(this.props.connection.id); - } - } - - componentWillUnmount() { - this.closeRenderWindow(); - } - - loadStream = (connectionId) => { - this.setState({ messages: [], currentId: connectionId }); - backend - .get(`/api/streams/${connectionId}?format=${this.state.format}`) - .then((res) => this.setState({ messages: res.json })); - }; - - setFormat = (format) => { - if (this.validFormats.includes(format)) { - this.setState({ format }); - } - }; - - viewAs = (mode) => { - if (mode === "decoded") { - this.setState({ tryParse: true }); - } else if (mode === "raw") { - this.setState({ tryParse: false }); - } - }; - - tryParseConnectionMessage = (connectionMessage) => { - const isClient = connectionMessage["from_client"]; - if (connectionMessage.metadata == null) { - return this.highlightRules(connectionMessage.content, isClient); - } - - let unrollMap = (obj) => - obj == null - ? null - : Object.entries(obj).map(([key, value]) => ( -

- {key}: {value} -

- )); - - let m = connectionMessage.metadata; - switch (m.type) { - case "http-request": - let url = ( - - - - {m.host} - {m.url} - - - - ); - return ( - -

- {m.method} {url} {m.protocol} -

- {unrollMap(m.headers)} -
- {this.highlightRules(m.body, isClient)} -
- {unrollMap(m.trailers)} -
- ); - case "http-response": - const contentType = getHeaderValue(m, "Content-Type"); - let body = m.body; - if (contentType && contentType.includes("application/json")) { - try { - const json = JSON.parse(m.body); - if (typeof json === "object") { - body = ( - - ); - } - } catch (e) { - log.error(e); - } - } - - return ( - -

- {m.protocol} {m.status} -

- {unrollMap(m.headers)} -
- {this.highlightRules(body, isClient)} -
- {unrollMap(m.trailers)} -
- ); - default: - return this.highlightRules(connectionMessage.content, isClient); - } - }; - - highlightRules = (content, isClient) => { - let streamContent = content; - this.props.connection["matched_rules"].forEach((ruleId) => { - const rule = rules.ruleById(ruleId); - rule.patterns.forEach((pattern) => { - if ( - (!isClient && pattern.direction === 1) || - (isClient && pattern.direction === 2) - ) { - return; - } - let flags = ""; - pattern["caseless"] && (flags += "i"); - pattern["dot_all"] && (flags += "s"); - pattern["multi_line"] && (flags += "m"); - pattern["unicode_property"] && (flags += "u"); - const regex = new RegExp( - pattern.regex.replace(/^\//, "(").replace(/\/$/, ")"), - flags - ); - streamContent = reactStringReplace(streamContent, regex, (match, i) => ( - - {match} - - )); - }); - }); - - return streamContent; - }; - - connectionsActions = (connectionMessage) => { - if (!connectionMessage.metadata) { - return null; - } - - const m = connectionMessage.metadata; - switch (m.type) { - case "http-request": - if (!connectionMessage.metadata["reproducers"]) { - return; - } - return Object.entries(connectionMessage.metadata["reproducers"]).map( - ([name, value]) => ( - { - this.setState({ - messageActionDialog: ( - - this.setState({ messageActionDialog: null }) - } - /> - ), - }); - }} - /> - ) - ); - case "http-response": - const contentType = getHeaderValue(m, "Content-Type"); - - if (contentType && contentType.includes("text/html")) { - return ( - { - let w; - if ( - this.state.renderWindow && - !this.state.renderWindow.closed - ) { - w = this.state.renderWindow; - } else { - w = window.open( - "", - "", - "width=900, height=600, scrollbars=yes" - ); - this.setState({ renderWindow: w }); - } - w.document.body.innerHTML = DOMPurify.sanitize(m.body); - w.focus(); - }} - /> - ); - } - break; - default: - return null; - } - }; - - downloadStreamRaw = (value) => { - if (this.state.currentId) { - backend - .download( - `/api/streams/${this.props.connection.id}/download?format=${this.state.format}&type=${value}` - ) - .then((res) => - downloadBlob( - res.blob, - `${this.state.currentId}-${value}-${this.state.format}.txt` - ) - ) - .catch((_) => log.error("Failed to download stream messages")); - } - }; - - closeRenderWindow = () => { - if (this.state.renderWindow) { - this.state.renderWindow.close(); - } - }; - - render() { - const conn = this.props.connection || { - ip_src: "0.0.0.0", - ip_dst: "0.0.0.0", - port_src: "0", - port_dst: "0", - started_at: new Date().toISOString(), - }; - const content = this.state.messages || []; - - let payload = content - .filter( - (c) => - !this.state.tryParse || - (this.state.tryParse && !c["is_metadata_continuation"]) - ) - .map((c, i) => ( -
-
-
-
- - offset: {c.index} - {" "} - |{" "} - - timestamp: {c.timestamp} - {" "} - |{" "} - - retransmitted:{" "} - {c["is_retransmitted"] ? "yes" : "no"} - -
-
- {this.connectionsActions(c)} -
-
-
-
- {c["from_client"] ? "client" : "server"} -
-
- {this.state.tryParse && this.state.format === "default" - ? this.tryParseConnectionMessage(c) - : c.content} -
-
- )); - - return ( -
-
- -
- - flow: {conn["ip_src"]}:{conn["port_src"]} ->{" "} - {conn["ip_dst"]}:{conn["port_dst"]} - - - {" "} - | timestamp: {conn["started_at"]} - -
-
- - - - - -
-
-
- -
{payload}
- {this.state.messageActionDialog} -
- ); - } -} - -export default StreamsPane; -- cgit v1.2.3-70-g09d2