aboutsummaryrefslogtreecommitdiff
path: root/frontend/src
diff options
context:
space:
mode:
authorEmiliano Ciavatta2020-09-30 11:29:04 +0000
committerEmiliano Ciavatta2020-09-30 11:29:04 +0000
commit43135c255d82aa7c54ea83b14369c93425ae75f6 (patch)
tree34a0f4e80fde301c3c4905c180b4b18882428811 /frontend/src
parentd994a21a0dfae9ee026e8aa3ccdee6c213c523aa (diff)
Complete pcap page
Diffstat (limited to 'frontend/src')
-rw-r--r--frontend/src/backend.js56
-rw-r--r--frontend/src/components/Connection.js10
-rw-r--r--frontend/src/components/ConnectionContent.js2
-rw-r--r--frontend/src/components/filters/RulesConnectionsFilter.js2
-rw-r--r--frontend/src/components/objects/LinkPopover.js2
-rw-r--r--frontend/src/components/panels/PcapPane.js289
-rw-r--r--frontend/src/components/panels/PcapPane.scss25
-rw-r--r--frontend/src/components/panels/RulePane.js6
-rw-r--r--frontend/src/components/panels/common.scss16
-rw-r--r--frontend/src/utils.js27
-rw-r--r--frontend/src/views/Connections.js6
-rw-r--r--frontend/src/views/MainPane.js2
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 = <div>
<span>Started at {startedAt.toLocaleDateString() + " " + startedAt.toLocaleTimeString()}</span><br/>
<span>Processed at {processedAt.toLocaleDateString() + " " + processedAt.toLocaleTimeString()}</span><br/>
@@ -108,7 +102,7 @@ class Connection extends Component {
<td className="clickable" onClick={this.props.onSelected}>
<OverlayTrigger trigger={["focus", "hover"]} placement="right"
overlay={popoverFor("duration", timeInfo)}>
- <span className="test-tooltip">{duration}</span>
+ <span className="test-tooltip">{durationBetween(startedAt, closedAt)}</span>
</OverlayTrigger>
</td>
<td className="clickable" onClick={this.props.onSelected}>{formatSize(conn.client_bytes)}</td>
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 ?
- <OverlayTrigger trigger="hover" placement={this.props.placement || "top"} overlay={popover}>
+ <OverlayTrigger trigger={["hover", "focus"]} placement={this.props.placement || "top"} overlay={popover}>
<span className="link-popover">{this.props.text}</span>
</OverlayTrigger> :
<span className="link-popover-empty">{this.props.text}</span>
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 =>
<tr className="table-row">
<td>{s["id"].substring(0, 8)}</td>
- <td>{timestampToTime2(s["started_at"])}</td>
- <td>{((new Date(s["completed_at"]) - new Date(s["started_at"])) / 1000).toFixed(3)}s</td>
+ <td>{dateTimeToTime(s["started_at"])}</td>
+ <td>{durationBetween(s["started_at"], s["completed_at"])}</td>
<td>{formatSize(s["size"])}</td>
<td>{s["processed_packets"]}</td>
<td>{s["invalid_packets"]}</td>
- <td>undefined</td>
+ <td><LinkPopover text={Object.keys(s["packets_per_service"]).length + " services"}
+ content={JSON.stringify(s["packets_per_service"])}
+ placement="left"/></td>
<td className="table-cell-action"><a target="_blank" rel="noopener noreferrer"
href={"/api/pcap/sessions/" + s["id"] + "/download"}>download</a>
</td>
</tr>
);
- 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 (
- <div className="pane-container">
- <div className="pane-section">
+ <div className="pane-container pcap-pane">
+ <div className="pane-section pcap-list">
<div className="section-header">
<span className="api-request">GET /api/pcap/sessions</span>
- <span className="api-response">200 OK</span>
+ <span className="api-response"><LinkPopover text={this.state.sessionsStatusCode}
+ content={this.state.sessionsResponse}
+ placement="left"/></span>
</div>
- <div className="section-table">
- <Table borderless size="sm">
- <thead>
- <tr>
- <th>id</th>
- <th>started_at</th>
- <th>duration</th>
- <th>size</th>
- <th>processed_packets</th>
- <th>invalid_packets</th>
- <th>packets_per_service</th>
- <th>actions</th>
- </tr>
- </thead>
- <tbody>
- {sessions}
- </tbody>
- </Table>
+ <div className="section-content">
+ <div className="section-table">
+ <Table borderless size="sm">
+ <thead>
+ <tr>
+ <th>id</th>
+ <th>started_at</th>
+ <th>duration</th>
+ <th>size</th>
+ <th>processed_packets</th>
+ <th>invalid_packets</th>
+ <th>packets_per_service</th>
+ <th>actions</th>
+ </tr>
+ </thead>
+ <tbody>
+ {sessions}
+ </tbody>
+ </Table>
+ </div>
</div>
</div>
- <div className="pane-section">
- <Container className="p-0">
- <Row>
- <Col style={{"paddingRight": "0"}}>
- <div className="section-header">
- <span className="api-request">POST /api/pcap/upload</span>
- <span className="api-response">{this.state.uploadStatusCode}</span>
+ <div className="double-pane-container">
+ <div className="pane-section">
+ <div className="section-header">
+ <span className="api-request">POST /api/pcap/upload</span>
+ <span className="api-response"><LinkPopover text={this.state.uploadStatusCode}
+ content={this.state.uploadResponse}
+ placement="left"/></span>
+ </div>
+
+ <div className="section-content">
+ <InputField type={"file"} name={"file"} invalid={!this.state.isUploadFileValid}
+ active={this.state.isUploadFileFocused}
+ onChange={handleUploadFileChange} value={this.state.uploadSelectedFile}
+ placeholder={"no .pcap[ng] selected"}/>
+ <div className="upload-actions">
+ <div className="upload-options">
+ <span>options:</span>
+ <CheckField name="flush_all" checked={this.state.uploadFlushAll}
+ onChange={v => this.setState({uploadFlushAll: v})}/>
</div>
+ <ButtonField variant="green" bordered onClick={this.uploadPcap} name="upload"/>
+ </div>
- <div className="section-content">
- <InputField type={"file"} name={"file"} invalid={!this.state.isUploadFileValid}
- active={this.state.isUploadFileFocused}
- onChange={this.handleUploadFileChange} value={this.state.uploadSelectedFile}
- defaultValue={"no .pcap[ng] selected"}/>
-
- <div className="upload-actions">
- <div className="upload-options">
- <span>options:</span>
- <CheckField name="flush_all" checked={this.state.uploadFlushAll}
- onChange={v => this.setState({uploadFlushAll: v})}/>
- </div>
- <ButtonField variant="green" bordered onClick={this.handleUploadPcap} name="upload" />
- </div>
-
- <TextField value={uploadOutput} rows={4} readonly small={true}/>
- </div>
- </Col>
+ <TextField value={uploadCurlCommand} rows={4} readonly small={true}/>
+ </div>
+ </div>
- <Col>
- <div className="section-header">
- <span className="api-request">POST /api/pcap/file</span>
- <span className="api-response">{this.state.fileStatusCode}</span>
+ <div className="pane-section">
+ <div className="section-header">
+ <span className="api-request">POST /api/pcap/file</span>
+ <span className="api-response"><LinkPopover text={this.state.processStatusCode}
+ content={this.state.processResponse}
+ placement="left"/></span>
+ </div>
+
+ <div className="section-content">
+ <InputField name="file" active={this.state.isFileFocused} invalid={!this.state.isFileValid}
+ onChange={handleFileChange} value={this.state.fileValue}
+ placeholder={"local .pcap[ng] path"} inline/>
+
+ <div className="upload-actions" style={{"marginTop": "11px"}}>
+ <div className="upload-options">
+ <CheckField name="flush_all" checked={this.state.processFlushAll}
+ onChange={v => this.setState({processFlushAll: v})}/>
+ <CheckField name="delete_original_file" checked={this.state.deleteOriginalFile}
+ onChange={v => this.setState({deleteOriginalFile: v})}/>
</div>
+ <ButtonField variant="blue" bordered onClick={this.processPcap} name="process"/>
+ </div>
- <div className="section-content">
- <InputField name="file" active={this.state.isUploadFileFocused}
- onChange={this.handleFileChange} value={this.state.uploadSelectedFile}
- defaultValue={"local .pcap[ng] path"} inline/>
-
- <div className="upload-actions" style={{"marginTop": "11px"}}>
- <div className="upload-options">
- <CheckField name="flush_all" checked={this.state.uploadFlushAll}
- onChange={v => this.setState({uploadFlushAll: v})}/>
- <CheckField name="delete_original_file" checked={this.state.uploadFlushAll}
- onChange={v => this.setState({uploadFlushAll: v})}/>
- </div>
- <ButtonField variant="blue" bordered onClick={this.handleUploadPcap} name="process" />
- </div>
-
- <TextField value={uploadOutput} rows={4} readonly small={true}/>
- </div>
- </Col>
- </Row>
- </Container>
+ <TextField value={fileCurlCommand} rows={4} readonly small={true}/>
+ </div>
+ </div>
</div>
</div>
);
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 {
<Connection key={c.id} data={c} onSelected={() => 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));
}