diff options
author | Emiliano Ciavatta | 2020-09-30 11:29:04 +0000 |
---|---|---|
committer | Emiliano Ciavatta | 2020-09-30 11:29:04 +0000 |
commit | 43135c255d82aa7c54ea83b14369c93425ae75f6 (patch) | |
tree | 34a0f4e80fde301c3c4905c180b4b18882428811 | |
parent | d994a21a0dfae9ee026e8aa3ccdee6c213c523aa (diff) |
Complete pcap page
-rw-r--r-- | frontend/src/backend.js | 56 | ||||
-rw-r--r-- | frontend/src/components/Connection.js | 10 | ||||
-rw-r--r-- | frontend/src/components/ConnectionContent.js | 2 | ||||
-rw-r--r-- | frontend/src/components/filters/RulesConnectionsFilter.js | 2 | ||||
-rw-r--r-- | frontend/src/components/objects/LinkPopover.js | 2 | ||||
-rw-r--r-- | frontend/src/components/panels/PcapPane.js | 289 | ||||
-rw-r--r-- | frontend/src/components/panels/PcapPane.scss | 25 | ||||
-rw-r--r-- | frontend/src/components/panels/RulePane.js | 6 | ||||
-rw-r--r-- | frontend/src/components/panels/common.scss | 16 | ||||
-rw-r--r-- | frontend/src/utils.js | 27 | ||||
-rw-r--r-- | frontend/src/views/Connections.js | 6 | ||||
-rw-r--r-- | 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 = <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)); } |