From 43135c255d82aa7c54ea83b14369c93425ae75f6 Mon Sep 17 00:00:00 2001
From: Emiliano Ciavatta
Date: Wed, 30 Sep 2020 13:29:04 +0200
Subject: Complete pcap page
---
frontend/src/backend.js | 56 +---
frontend/src/components/Connection.js | 10 +-
frontend/src/components/ConnectionContent.js | 2 +-
.../components/filters/RulesConnectionsFilter.js | 2 +-
frontend/src/components/objects/LinkPopover.js | 2 +-
frontend/src/components/panels/PcapPane.js | 289 ++++++++++++---------
frontend/src/components/panels/PcapPane.scss | 25 +-
frontend/src/components/panels/RulePane.js | 6 +-
frontend/src/components/panels/common.scss | 16 ++
frontend/src/utils.js | 27 +-
frontend/src/views/Connections.js | 6 +-
frontend/src/views/MainPane.js | 2 +-
12 files changed, 247 insertions(+), 196 deletions(-)
diff --git a/frontend/src/backend.js b/frontend/src/backend.js
index 2fbe920..72ee9dd 100644
--- a/frontend/src/backend.js
+++ b/frontend/src/backend.js
@@ -1,7 +1,8 @@
-async function json(method, url, data, headers) {
+async function json(method, url, data, json, headers) {
const options = {
method: method,
+ body: json != null ? JSON.stringify(json) : data,
mode: "cors",
cache: "no-cache",
credentials: "same-origin",
@@ -11,9 +12,6 @@ async function json(method, url, data, headers) {
redirect: "follow",
referrerPolicy: "no-referrer",
};
- if (data != null) {
- options.body = JSON.stringify(data);
- }
const response = await fetch(url, options);
const result = {
statusCode: response.status,
@@ -28,47 +26,17 @@ async function json(method, url, data, headers) {
}
}
-async function file(url, data, headers) {
- const options = {
- method: "POST",
- mode: "cors",
- cache: "no-cache",
- credentials: "same-origin",
- body: data,
- redirect: "follow",
- referrerPolicy: "no-referrer",
- };
- return await fetch(url, options);
-}
-
const backend = {
- get: (url = "", headers = null) => {
- return json("GET", url, null, headers);
- },
- post: (url = "", data = null, headers = null) => {
- return json("POST", url, data, headers);
- },
- put: (url = "", data = null, headers = null) => {
- return json("PUT", url, data, headers);
- },
- delete: (url = "", data = null, headers = null) => {
- return json("DELETE", url, data, headers);
- },
- getJson: (url = "", headers = null) => {
- return json("GET", url, null, headers);
- },
- postJson: (url = "", data = null, headers = null) => {
- return json("POST", url, data, headers);
- },
- putJson: (url = "", data = null, headers = null) => {
- return json("PUT", url, data, headers);
- },
- deleteJson: (url = "", data = null, headers = null) => {
- return json("DELETE", url, data, headers);
- },
- postFile: (url = "", data = null, headers = null) => {
- return file(url, data, headers);
- },
+ get: (url = "", headers = null) =>
+ json("GET", url, null,null, headers),
+ post: (url = "", data = null, headers = null) =>
+ json("POST", url, null, data, headers),
+ put: (url = "", data = null, headers = null) =>
+ json("PUT", url, null, data, headers),
+ delete: (url = "", data = null, headers = null) =>
+ json("DELETE", url, null, data, headers),
+ postFile: (url = "", data = null, headers = {}) =>
+ json("POST", url, data, null, headers)
};
export default backend;
diff --git a/frontend/src/components/Connection.js b/frontend/src/components/Connection.js
index 7887185..95b27ff 100644
--- a/frontend/src/components/Connection.js
+++ b/frontend/src/components/Connection.js
@@ -2,7 +2,7 @@ import React, {Component} from 'react';
import './Connection.scss';
import {Form, OverlayTrigger, Popover} from "react-bootstrap";
import backend from "../backend";
-import {formatSize} from "../utils";
+import {durationBetween, formatSize} from "../utils";
import ButtonField from "./fields/ButtonField";
class Connection extends Component {
@@ -54,12 +54,6 @@ class Connection extends Component {
let startedAt = new Date(conn.started_at);
let closedAt = new Date(conn.closed_at);
let processedAt = new Date(conn.processed_at);
- let duration = ((closedAt - startedAt) / 1000).toFixed(3);
- if (duration > 1000 || duration < -1000) {
- duration = "∞";
- } else {
- duration += "s";
- }
let timeInfo =
Started at {startedAt.toLocaleDateString() + " " + startedAt.toLocaleTimeString()}
Processed at {processedAt.toLocaleDateString() + " " + processedAt.toLocaleTimeString()}
@@ -108,7 +102,7 @@ class Connection extends Component {
- {duration}
+ {durationBetween(startedAt, closedAt)}
|
{formatSize(conn.client_bytes)} |
diff --git a/frontend/src/components/ConnectionContent.js b/frontend/src/components/ConnectionContent.js
index a9d34d3..6bc0c96 100644
--- a/frontend/src/components/ConnectionContent.js
+++ b/frontend/src/components/ConnectionContent.js
@@ -40,7 +40,7 @@ class ConnectionContent extends Component {
loadStream = () => {
this.setState({loading: true});
// TODO: limit workaround.
- backend.getJson(`/api/streams/${this.props.connection.id}?format=${this.state.format}&limit=999999`).then(res => {
+ backend.get(`/api/streams/${this.props.connection.id}?format=${this.state.format}&limit=999999`).then(res => {
this.setState({
connectionContent: res.json,
loading: false
diff --git a/frontend/src/components/filters/RulesConnectionsFilter.js b/frontend/src/components/filters/RulesConnectionsFilter.js
index f4d0b1c..8366189 100644
--- a/frontend/src/components/filters/RulesConnectionsFilter.js
+++ b/frontend/src/components/filters/RulesConnectionsFilter.js
@@ -24,7 +24,7 @@ class RulesConnectionsFilter extends Component {
let params = new URLSearchParams(this.props.location.search);
let activeRules = params.getAll("matched_rules") || [];
- backend.getJson("/api/rules").then(res => {
+ backend.get("/api/rules").then(res => {
let rules = res.json.flatMap(rule => rule.enabled ? [{id: rule.id, name: rule.name}] : []);
activeRules = rules.filter(rule => activeRules.some(id => rule.id === id));
this.setState({rules, activeRules, mounted: true});
diff --git a/frontend/src/components/objects/LinkPopover.js b/frontend/src/components/objects/LinkPopover.js
index 58b2f6a..8768caa 100644
--- a/frontend/src/components/objects/LinkPopover.js
+++ b/frontend/src/components/objects/LinkPopover.js
@@ -22,7 +22,7 @@ class LinkPopover extends Component {
);
return (this.props.content ?
-
+
{this.props.text}
:
{this.props.text}
diff --git a/frontend/src/components/panels/PcapPane.js b/frontend/src/components/panels/PcapPane.js
index cfba037..e83e3da 100644
--- a/frontend/src/components/panels/PcapPane.js
+++ b/frontend/src/components/panels/PcapPane.js
@@ -1,14 +1,14 @@
import React, {Component} from 'react';
-// import './PcapPane.scss';
+import './PcapPane.scss';
import './common.scss';
import Table from "react-bootstrap/Table";
import backend from "../../backend";
-import {createCurlCommand, formatSize, timestampToTime2} from "../../utils";
-import {Col, Container, Row} from "react-bootstrap";
+import {createCurlCommand, dateTimeToTime, durationBetween, formatSize} from "../../utils";
import InputField from "../fields/InputField";
import CheckField from "../fields/CheckField";
import TextField from "../fields/TextField";
import ButtonField from "../fields/ButtonField";
+import LinkPopover from "../objects/LinkPopover";
class PcapPane extends Component {
@@ -19,16 +19,11 @@ class PcapPane extends Component {
sessions: [],
isUploadFileValid: true,
isUploadFileFocused: false,
- uploadSelectedFile: null,
uploadFlushAll: false,
- uploadStatusCode: null,
- uploadOutput: null,
isFileValid: true,
isFileFocused: false,
fileValue: "",
- fileFlushAll: false,
- fileStatusCode: null,
- fileOutput: null,
+ processFlushAll: false,
deleteOriginalFile: false
};
}
@@ -38,46 +33,38 @@ class PcapPane extends Component {
}
loadSessions = () => {
- backend.getJson("/api/pcap/sessions").then(res => this.setState({sessions: res.json}));
+ 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)
+ }));
};
- handleUploadFileChange = (file) => {
- this.setState({
- isUploadFileValid: file != null && file.type.endsWith("pcap"),
- isUploadFileFocused: false,
- uploadSelectedFile: file
- });
- };
-
- handleUploadPcap = () => {
+ uploadPcap = () => {
if (this.state.uploadSelectedFile == null || !this.state.isUploadFileValid) {
this.setState({isUploadFileFocused: true});
return;
}
const formData = new FormData();
- formData.append(
- "file",
- this.state.uploadSelectedFile
- );
-
- backend.postFile("/api/pcap/upload", formData).then(response =>
- response.json().then(result => this.setState({
- uploadStatusCode: response.status + " " + response.statusText,
- uploadOutput: JSON.stringify(result)
- }))
+ 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)
+ })
);
};
- handleFileChange = (file) => {
- this.setState({
- isFileValid: file !== "" && file.endsWith("pcap"),
- isFileFocused: false,
- fileValue: file
- });
- };
-
- handleProcessPcap = () => {
+ processPcap = () => {
if (this.state.fileValue === "" || !this.state.isFileValid) {
this.setState({isFileFocused: true});
return;
@@ -85,123 +72,177 @@ class PcapPane extends Component {
backend.post("/api/pcap/file", {
file: this.state.fileValue,
- flush_all: this.state.fileFlushAll,
+ flush_all: this.state.processFlushAll,
delete_original_file: this.state.deleteOriginalFile
- }).then(response =>
- response.json().then(result => this.setState({
- fileStatusCode: response.status + " " + response.statusText,
- fileOutput: JSON.stringify(result)
- }))
+ }).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 =>
{s["id"].substring(0, 8)} |
- {timestampToTime2(s["started_at"])} |
- {((new Date(s["completed_at"]) - new Date(s["started_at"])) / 1000).toFixed(3)}s |
+ {dateTimeToTime(s["started_at"])} |
+ {durationBetween(s["started_at"], s["completed_at"])} |
{formatSize(s["size"])} |
{s["processed_packets"]} |
{s["invalid_packets"]} |
- undefined |
+ |
download
|
);
- const uploadOutput = this.state.uploadOutput != null ? this.state.uploadOutput :
- 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 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
- 200 OK
+
-
-
-
-
- id |
- started_at |
- duration |
- size |
- processed_packets |
- invalid_packets |
- packets_per_service |
- actions |
-
-
-
- {sessions}
-
-
+
+
+
+
+
+ id |
+ started_at |
+ duration |
+ size |
+ processed_packets |
+ invalid_packets |
+ packets_per_service |
+ actions |
+
+
+
+ {sessions}
+
+
+
-
-
-
-
-
-
POST /api/pcap/upload
-
{this.state.uploadStatusCode}
+
+
+
+ POST /api/pcap/upload
+
+
+
+
+
+
+
+ options:
+ this.setState({uploadFlushAll: v})}/>
+
+
-
-
-
-
-
- options:
- this.setState({uploadFlushAll: v})}/>
-
-
-
-
-
-
-
+
+
+
-
-
-
POST /api/pcap/file
-
{this.state.fileStatusCode}
+
+
+ POST /api/pcap/file
+
+
+
+
+
+
+
+
+ this.setState({processFlushAll: v})}/>
+ this.setState({deleteOriginalFile: v})}/>
+
+
-
-
-
-
-
- this.setState({uploadFlushAll: v})}/>
- this.setState({uploadFlushAll: v})}/>
-
-
-
-
-
-
-
-
-
+
+
+
);
diff --git a/frontend/src/components/panels/PcapPane.scss b/frontend/src/components/panels/PcapPane.scss
index 3c4d10b..721560a 100644
--- a/frontend/src/components/panels/PcapPane.scss
+++ b/frontend/src/components/panels/PcapPane.scss
@@ -1,10 +1,25 @@
@import '../../colors.scss';
-.pane-container {
+.pcap-pane {
+ display: flex;
+ flex-direction: column;
- .table-cell-action {
- font-size: 13px;
- font-weight: 600;
+ .pcap-list {
+ flex: 1;
+ overflow: hidden;
+
+ .section-content {
+ height: 100%;
+ }
+
+ .section-table {
+ height: calc(100% - 30px);
+ }
+
+ .table-cell-action {
+ font-size: 13px;
+ font-weight: 600;
+ }
}
.upload-actions {
@@ -21,4 +36,4 @@
}
}
-}
\ No newline at end of file
+}
diff --git a/frontend/src/components/panels/RulePane.js b/frontend/src/components/panels/RulePane.js
index 01e37fa..d4c5460 100644
--- a/frontend/src/components/panels/RulePane.js
+++ b/frontend/src/components/panels/RulePane.js
@@ -73,13 +73,13 @@ class RulePane extends Component {
};
loadRules = () => {
- backend.getJson("/api/rules").then(res => this.setState({rules: res.json, rulesStatusCode: res.status}))
+ 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.postJson("/api/rules", this.state.newRule).then(res => {
+ backend.post("/api/rules", this.state.newRule).then(res => {
this.reset();
this.setState({ruleStatusCode: res.status});
this.loadRules();
@@ -92,7 +92,7 @@ class RulePane extends Component {
updateRule = () => {
const rule = this.state.selectedRule;
if (this.validateRule(rule)) {
- backend.putJson(`/api/rules/${rule.id}`, rule).then(res => {
+ backend.put(`/api/rules/${rule.id}`, rule).then(res => {
this.reset();
this.setState({ruleStatusCode: res.status});
this.loadRules();
diff --git a/frontend/src/components/panels/common.scss b/frontend/src/components/panels/common.scss
index cab0de1..ea8da94 100644
--- a/frontend/src/components/panels/common.scss
+++ b/frontend/src/components/panels/common.scss
@@ -82,4 +82,20 @@
margin-left: 10px;
}
}
+
+ .double-pane-container {
+ display: flex;
+
+ .pane-section {
+ flex: 1;
+ }
+
+ .pane-section:nth-child(1) {
+ margin-right: 5px;
+ }
+
+ .pane-section:nth-child(2) {
+ margin-left: 5px;
+ }
+ }
}
diff --git a/frontend/src/utils.js b/frontend/src/utils.js
index 6a5411c..0707575 100644
--- a/frontend/src/utils.js
+++ b/frontend/src/utils.js
@@ -1,3 +1,5 @@
+import React from "react";
+
const timeRegex = /^(0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/;
export function createCurlCommand(subCommand, method = null, json = null, data = null) {
@@ -75,14 +77,29 @@ export function timestampToDateTime(timestamp) {
return d.toLocaleDateString() + " " + d.toLocaleTimeString();
}
-export function timestampToTime2(timestamp) {
- let d = new Date(timestamp);
- let hours = d.getHours();
- let minutes = "0" + d.getMinutes();
- let seconds = "0" + d.getSeconds();
+export function dateTimeToTime(dateTime) {
+ if (typeof dateTime === "string") {
+ dateTime = new Date(dateTime);
+ }
+
+ let hours = dateTime.getHours();
+ let minutes = "0" + dateTime.getMinutes();
+ let seconds = "0" + dateTime.getSeconds();
return hours + ':' + minutes.substr(-2) + ':' + seconds.substr(-2);
}
+export function durationBetween(from, to) {
+ if (typeof from === "string") {
+ from = new Date(from);
+ }
+ if (typeof to === "string") {
+ to = new Date(to);
+ }
+ const duration = ((to - from) / 1000).toFixed(3);
+
+ return (duration > 1000 || duration < -1000) ? "∞" : duration + "s";
+}
+
export function formatSize(size) {
if (size < 1000) {
return `${size}`;
diff --git a/frontend/src/views/Connections.js b/frontend/src/views/Connections.js
index 64068c5..9dca7e9 100644
--- a/frontend/src/views/Connections.js
+++ b/frontend/src/views/Connections.js
@@ -75,7 +75,7 @@ class Connections extends Component {
}
this.setState({loading: true, prevParams: params});
- let res = (await backend.getJson(`${url}?${urlParams}`)).json;
+ let res = (await backend.get(`${url}?${urlParams}`)).json;
let connections = this.state.connections;
let firstConnection = this.state.firstConnection;
@@ -115,7 +115,7 @@ class Connections extends Component {
let flagRule = this.state.flagRule;
let rules = this.state.rules;
if (flagRule === null) {
- rules = (await backend.getJson("/api/rules")).json;
+ rules = (await backend.get("/api/rules")).json;
flagRule = rules.filter(rule => {
return rule.name === "flag";
})[0];
@@ -170,7 +170,7 @@ class Connections extends Component {
this.connectionSelected(c)}
selected={this.state.selected === c.id} onMarked={marked => c.marked = marked}
onEnabled={enabled => c.hidden = !enabled}
- containsFlag={c.matched_rules.includes(this.state.flagRule.id)}
+ containsFlag={this.state.flagRule && c.matched_rules.includes(this.state.flagRule.id)}
addServicePortFilter={this.addServicePortFilter}/>
)
}
diff --git a/frontend/src/views/MainPane.js b/frontend/src/views/MainPane.js
index 6f4e3cd..b9ebadb 100644
--- a/frontend/src/views/MainPane.js
+++ b/frontend/src/views/MainPane.js
@@ -21,7 +21,7 @@ class MainPane extends Component {
const match = this.props.location.pathname.match(/^\/connections\/([a-f0-9]{24})$/);
if (match != null) {
this.setState({loading: true});
- backend.getJson(`/api/connections/${match[1]}`)
+ backend.get(`/api/connections/${match[1]}`)
.then(res => this.setState({selectedConnection: res.json, loading: false}))
.catch(error => console.log(error));
}
--
cgit v1.2.3-70-g09d2