;
- 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");
- });
- }}/>
-
}
-
-
-
-
-
- service |
- srcip |
- srcport |
- dstip |
- dstport |
- started_at |
- duration |
- up |
- down |
- actions |
-
-
-
- {
- 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}
-
-
-
- {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
-
-
-
-
-
-
-
-
- id |
- started_at |
- duration |
- size |
- processed_packets |
- invalid_packets |
- packets_per_service |
- actions |
-
-
-
- {sessions}
-
-
-
-
-
-
-
-
-
- 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 &&
- }
-
-
-
-
-
-
-
- id |
- name |
- color |
- notes |
-
-
-
- {rules}
-
-
-
-
-
-
-
-
-
- {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)} />
-
-
-
-
-
-
-
-
- regex |
- !Aa |
- .* |
- \n+ |
- UTF8 |
- Uni_ |
- min |
- max |
- direction |
- {!isUpdate && actions | }
-
-
-
- {patterns}
-
-
- {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 &&
- }
-
-
-
-
-
-
-
- id |
- pattern |
- occurrences |
- started_at |
- duration |
- actions |
-
-
-
- {searches}
-
-
-
-
-
-
-
-
- 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 &&
- }
-
-
-
-
-
-
-
- port |
- name |
- color |
- notes |
-
-
-
- {services}
-
-
-
-
-
-
-
-
- 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
-
-
-
-
-
-
-
-
- service |
- connections |
- client_bytes |
- server_bytes |
- total_bytes |
- duration |
-
-
-
- {servicesStats}
-
-
-
-
-
-
-
-
- rule_id |
- rule_name |
- rule_color |
- occurrences |
-
-
-
- {rulesStats}
-
-
-
-
-
-
-
-
- GET /api/resources/system
-
-
-
-
-
-
-
-
- type |
- total |
- used |
- free |
- shared |
- buff/cache |
- available |
-
-
-
-
- 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"])} |
- - |
- - |
- - |
-
-
-
-
-
-
-
-
-
- cpu |
- user |
- system |
- idle |
- nice |
- iowait |
- used_percent |
-
-
-
- {cpuStats}
-
-
-
-
-
-
-
-
- disk_path |
- fs_type |
- total |
- free |
- used |
- used_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