diff options
Diffstat (limited to 'frontend/src/components')
-rw-r--r-- | frontend/src/components/fields/CheckField.js (renamed from frontend/src/components/fields/BooleanField.js) | 9 | ||||
-rw-r--r-- | frontend/src/components/fields/CheckField.scss (renamed from frontend/src/components/fields/BooleanField.scss) | 3 | ||||
-rw-r--r-- | frontend/src/components/fields/InputField.js (renamed from frontend/src/components/fields/StringField.js) | 29 | ||||
-rw-r--r-- | frontend/src/components/fields/InputField.scss (renamed from frontend/src/components/fields/StringField.scss) | 76 | ||||
-rw-r--r-- | frontend/src/components/fields/TextField.js | 42 | ||||
-rw-r--r-- | frontend/src/components/fields/TextField.scss | 79 | ||||
-rw-r--r-- | frontend/src/components/filters/BooleanConnectionsFilter.js | 6 | ||||
-rw-r--r-- | frontend/src/components/filters/StringConnectionsFilter.js | 8 | ||||
-rw-r--r-- | frontend/src/components/panels/PcapPane.js | 86 | ||||
-rw-r--r-- | frontend/src/components/panels/PcapPane.scss | 14 |
10 files changed, 286 insertions, 66 deletions
diff --git a/frontend/src/components/fields/BooleanField.js b/frontend/src/components/fields/CheckField.js index 06a6da7..5cceac4 100644 --- a/frontend/src/components/fields/BooleanField.js +++ b/frontend/src/components/fields/CheckField.js @@ -1,10 +1,10 @@ import React, {Component} from 'react'; -import './BooleanField.scss'; +import './CheckField.scss'; import {randomClassName} from "../../utils"; const classNames = require('classnames'); -class BooleanField extends Component { +class CheckField extends Component { constructor(props) { super(props); @@ -13,7 +13,6 @@ class BooleanField extends Component { } render() { - const checked = this.props.checked || false; const small = this.props.small || false; const name = this.props.name || null; @@ -24,7 +23,7 @@ class BooleanField extends Component { }; return ( - <div className={classNames( "boolean-field", {"field-checked" : checked}, {"field-small": small})}> + <div className={classNames( "check-field", {"field-checked" : checked}, {"field-small": small})}> <div className="field-input"> <input type="checkbox" id={this.id} checked={checked} onChange={handler} /> <label htmlFor={this.id}>{(checked ? "✓ " : "✗ ") + name}</label> @@ -34,4 +33,4 @@ class BooleanField extends Component { } } -export default BooleanField; +export default CheckField; diff --git a/frontend/src/components/fields/BooleanField.scss b/frontend/src/components/fields/CheckField.scss index 6ec25f7..7b0ac5f 100644 --- a/frontend/src/components/fields/BooleanField.scss +++ b/frontend/src/components/fields/CheckField.scss @@ -1,7 +1,8 @@ @import '../../colors.scss'; -.boolean-field { +.check-field { font-size: 0.9em; + margin: 5px 0; .field-input { border-radius: 5px; diff --git a/frontend/src/components/fields/StringField.js b/frontend/src/components/fields/InputField.js index 7781b2d..af3b3df 100644 --- a/frontend/src/components/fields/StringField.js +++ b/frontend/src/components/fields/InputField.js @@ -1,10 +1,10 @@ import React, {Component} from 'react'; -import './StringField.scss'; +import './InputField.scss'; import {randomClassName} from "../../utils"; const classNames = require('classnames'); -class StringField extends Component { +class InputField extends Component { constructor(props) { super(props); @@ -13,7 +13,6 @@ class StringField extends Component { } render() { - const active = this.props.active || false; const invalid = this.props.invalid || false; const small = this.props.small || false; @@ -22,31 +21,41 @@ class StringField extends Component { const value = this.props.value || ""; const type = this.props.type || "text"; const error = this.props.error || null; + const defaultValue = this.props.defaultValue || null; const handler = (e) => { if (this.props.onChange) { - if (e == null) { + if (type === "file") { + let file = e.target.files[0]; + this.props.onChange(file); + } else if (e == null) { this.props.onChange(""); } else { this.props.onChange(e.target.value); } } }; + let inputProps = {}; + if (type !== "file") { + inputProps["value"] = value; + } return ( - <div className={classNames("string-field", {"field-active" : active}, {"field-invalid": invalid}, + <div className={classNames("input-field", {"field-active" : active}, {"field-invalid": invalid}, {"field-small": small}, {"field-inline": inline})}> <div className="field-wrapper"> { name && <div className="field-name"> - <label id={this.id}>{name}:</label> + <label>{name}:</label> </div> } <div className="field-input"> <div className="field-value"> - <input type={type} placeholder={this.props.defaultValue} aria-label={name} - aria-describedby={this.id} onChange={handler} value={value} /> + { type === "file" && <label for={this.id} className={"file-label"}> + {value.name || defaultValue}</label> } + <input type={type} placeholder={defaultValue} id={this.id} + aria-describedby={this.id} onChange={handler} {...inputProps} /> </div> - { value !== "" && + { type !== "file" && value !== "" && <div className="field-clear"> <span onClick={() => handler(null)}>del</span> </div> @@ -63,4 +72,4 @@ class StringField extends Component { } } -export default StringField; +export default InputField; diff --git a/frontend/src/components/fields/StringField.scss b/frontend/src/components/fields/InputField.scss index 2523c8d..cdb8c9f 100644 --- a/frontend/src/components/fields/StringField.scss +++ b/frontend/src/components/fields/InputField.scss @@ -1,7 +1,8 @@ @import '../../colors.scss'; -.string-field { +.input-field { font-size: 0.9em; + margin: 5px 0; .field-name { label { @@ -12,31 +13,52 @@ .field-input { position: relative; - .field-value input { - background-color: $color-primary-2; - width: 100%; - border: none; - color: $color-primary-4; - border-radius: 5px; - padding: 7px 10px; - - &:focus { - background-color: $color-primary-1; - color: $color-primary-4; - box-shadow: none; - outline: none; - } - - &[readonly] { + .field-value { + input, .file-label { background-color: $color-primary-2; + width: 100%; border: none; color: $color-primary-4; + border-radius: 5px; + padding: 7px 10px; + + &:focus { + background-color: $color-primary-1; + color: $color-primary-4; + box-shadow: none; + outline: none; + } + + &[readonly] { + background-color: $color-primary-2; + border: none; + color: $color-primary-4; + } + + &[readonly]:focus { + background-color: $color-primary-1; + color: $color-primary-4; + box-shadow: none; + } + } + + input[type="file"] { + display: none; + } + + .file-label { + margin: 0; } - &[readonly]:focus { + .file-label:after { + content: "Browse"; + position: absolute; + right: 0; + top: 0; + padding: 7px 10px 7px 12px; background-color: $color-primary-1; - color: $color-primary-4; - box-shadow: none; + border-bottom-right-radius: 5px; + border-top-right-radius: 5px; } } } @@ -47,10 +69,14 @@ color: $color-primary-3 !important; } - .field-value input { + .field-value input, .field-value .file-label { background-color: $color-primary-4 !important; color: $color-primary-3 !important; } + + .file-label:after { + background-color: $color-secondary-4 !important; + } } &.field-invalid { @@ -59,10 +85,14 @@ color: $color-primary-4 !important; } - .field-value input { + .field-value input, .field-value .file-label { background-color: $color-secondary-2 !important; color: $color-primary-4 !important; } + + .file-label:after { + background-color: $color-secondary-1 !important; + } } &.field-small { @@ -82,7 +112,7 @@ .field-input { width: 100%; - input { + input, .file-label { border-bottom-left-radius: 0; border-top-left-radius: 0; padding-left: 3px; diff --git a/frontend/src/components/fields/TextField.js b/frontend/src/components/fields/TextField.js new file mode 100644 index 0000000..86b98ed --- /dev/null +++ b/frontend/src/components/fields/TextField.js @@ -0,0 +1,42 @@ +import React, {Component} from 'react'; +import './TextField.scss'; +import {randomClassName} from "../../utils"; + +const classNames = require('classnames'); + +class TextField extends Component { + + constructor(props) { + super(props); + + this.id = `field-${this.props.name || "noname"}-${randomClassName()}`; + } + + render() { + const name = this.props.name || null; + const error = this.props.error || null; + const rows = this.props.rows || 3; + + const handler = (e) => { + if (this.props.onChange) { + if (e == null) { + this.props.onChange(""); + } else { + this.props.onChange(e.target.value); + } + } + }; + + return ( + <div className={classNames("text-field", {"field-active": this.props.active}, + {"field-invalid": this.props.invalid}, {"field-small": this.props.small})}> + {name && <label htmlFor={this.id}>{name}:</label>} + <textarea id={this.id} placeholder={this.props.defaultValue} onChange={handler} rows={rows} + readOnly={this.props.readonly} value={this.props.value} /> + {error && <div className="field-error">error: {error}</div>} + </div> + ); + } +} + +export default TextField; diff --git a/frontend/src/components/fields/TextField.scss b/frontend/src/components/fields/TextField.scss new file mode 100644 index 0000000..606f537 --- /dev/null +++ b/frontend/src/components/fields/TextField.scss @@ -0,0 +1,79 @@ +@import '../../colors.scss'; + +.text-field { + font-size: 0.9em; + margin: 5px 0; + + label { + display: block; + margin: 0; + } + + textarea { + background-color: $color-primary-2; + width: 100%; + border: none; + color: $color-primary-4; + border-radius: 5px; + padding: 7px 10px; + resize: none; + + &:focus { + background-color: $color-primary-1; + color: $color-primary-4; + box-shadow: none; + outline: none; + } + + &[readonly] { + background-color: $color-primary-2; + border: none; + color: $color-primary-4; + } + + &[readonly]:focus { + background-color: $color-primary-1; + color: $color-primary-4; + box-shadow: none; + } + } + + &.field-active { + textarea { + background-color: $color-primary-4 !important; + color: $color-primary-3 !important; + } + } + + &.field-invalid { + textarea { + background-color: $color-secondary-2 !important; + color: $color-primary-4 !important; + } + } + + &.field-small { + font-size: 0.8em; + } + + .field-clear { + position: absolute; + right: 8px; + top: 8px; + z-index: 10; + font-size: 0.9em; + font-weight: 600; + letter-spacing: -0.5px; + cursor: pointer; + } + + &.field-active .field-clear { + color: $color-primary-2; + } + + .field-error { + padding: 5px 10px; + font-size: 0.9em; + color: $color-secondary-0; + } +} diff --git a/frontend/src/components/filters/BooleanConnectionsFilter.js b/frontend/src/components/filters/BooleanConnectionsFilter.js index 490d185..4c5a78a 100644 --- a/frontend/src/components/filters/BooleanConnectionsFilter.js +++ b/frontend/src/components/filters/BooleanConnectionsFilter.js @@ -1,7 +1,7 @@ import React, {Component} from 'react'; import {withRouter} from "react-router-dom"; import {Redirect} from "react-router"; -import BooleanField from "../fields/BooleanField"; +import CheckField from "../fields/CheckField"; class BooleanConnectionsFilter extends Component { @@ -56,8 +56,8 @@ class BooleanConnectionsFilter extends Component { return ( <div className="filter" style={{"width": `${this.props.width}px`}}> - <BooleanField checked={this.toBoolean(this.state.filterActive)} name={this.props.filterName} - onChange={this.filterChanged} /> + <CheckField checked={this.toBoolean(this.state.filterActive)} name={this.props.filterName} + onChange={this.filterChanged} /> {redirect} </div> ); diff --git a/frontend/src/components/filters/StringConnectionsFilter.js b/frontend/src/components/filters/StringConnectionsFilter.js index 0d7f063..a304198 100644 --- a/frontend/src/components/filters/StringConnectionsFilter.js +++ b/frontend/src/components/filters/StringConnectionsFilter.js @@ -1,7 +1,7 @@ import React, {Component} from 'react'; import {withRouter} from "react-router-dom"; import {Redirect} from "react-router"; -import StringField from "../fields/StringField"; +import InputField from "../fields/InputField"; class StringConnectionsFilter extends Component { @@ -114,9 +114,9 @@ class StringConnectionsFilter extends Component { return ( <div className="filter" style={{"width": `${this.props.width}px`}}> - <StringField active={active} invalid={this.state.invalidValue} name={this.props.filterName} - defaultValue={this.props.defaultFilterValue} onChange={this.filterChanged} - value={this.state.fieldValue} inline={true} small={true} /> + <InputField active={active} invalid={this.state.invalidValue} name={this.props.filterName} + defaultValue={this.props.defaultFilterValue} onChange={this.filterChanged} + value={this.state.fieldValue} inline={true} small={true} /> {redirect} </div> ); diff --git a/frontend/src/components/panels/PcapPane.js b/frontend/src/components/panels/PcapPane.js index 9f3bc19..701edf2 100644 --- a/frontend/src/components/panels/PcapPane.js +++ b/frontend/src/components/panels/PcapPane.js @@ -2,10 +2,11 @@ import React, {Component} from 'react'; import './PcapPane.scss'; import Table from "react-bootstrap/Table"; import backend from "../../backend"; -import {formatSize, timestampToTime2} from "../../utils"; -import {Container, Row, Col, Form} from "react-bootstrap"; -import StringField from "../fields/StringField"; -import BooleanField from "../fields/BooleanField"; +import {createCurlCommand, formatSize, timestampToTime2} from "../../utils"; +import {Button, Col, Container, Form, Row} from "react-bootstrap"; +import InputField from "../fields/InputField"; +import CheckField from "../fields/CheckField"; +import TextField from "../fields/TextField"; class PcapPane extends Component { @@ -14,10 +15,17 @@ class PcapPane extends Component { this.state = { sessions: [], - test: false + isFileValid: true, + isFileFocused: false, + selectedFile: null, + uploadFlushAll: false, + uploadStatusCode: null, + uploadOutput: null }; this.loadSessions = this.loadSessions.bind(this); + this.handleFileChange = this.handleFileChange.bind(this); + this.handleUploadPcap = this.handleUploadPcap.bind(this); } componentDidMount() { @@ -28,6 +36,34 @@ class PcapPane extends Component { backend.get("/api/pcap/sessions").then(res => this.setState({sessions: res})); } + handleFileChange(file) { + this.setState({ + isFileValid: file != null && file.type.endsWith("pcap"), + isFileFocused: false, + selectedFile: file + }); + } + + handleUploadPcap() { + if (this.state.selectedFile == null || !this.state.isFileValid) { + this.setState({isFileFocused: true}); + return; + } + + const formData = new FormData(); + formData.append( + "file", + this.state.selectedFile + ); + + backend.postFile("/api/pcap/upload", formData).then(response => + response.json().then(result => this.setState({ + uploadStatusCode: response.status + " " + response.statusText, + uploadOutput: JSON.stringify(result) + })) + ); + } + render() { let sessions = this.state.sessions.map(s => <tr className="table-row"> @@ -38,10 +74,20 @@ class PcapPane extends Component { <td>{s["processed_packets"]}</td> <td>{s["invalid_packets"]}</td> <td>undefined</td> - <td className="table-cell-action"><a target="_blank" href={"/api/pcap/sessions/" + s["id"] + "/download"}>download</a></td> + <td className="table-cell-action"><a target="_blank" + 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.selectedFile != null && this.state.isFileValid) ? this.state.selectedFile.name : + "invalid.pcap"), + flush_all: this.state.uploadFlushAll + }) + ; + return ( <div className="pane-container"> <div className="pane-section"> @@ -77,19 +123,25 @@ class PcapPane extends Component { <Col> <div className="section-header"> <span className="api-request">POST /api/pcap/upload</span> - <span className="api-response"></span> + <span className="api-response">{this.state.uploadStatusCode}</span> </div> <div className="section-content"> - <Form.File className="custom-file" onChange={this.onFileChange} - label=".pcap/.pcapng" id="custom-file" - custom={true} - /> - - - <br/><br/><br/><br/> - <BooleanField small={true} name={"marked"} checked={this.state.test} onChange={(v) => this.setState({test: v})} /> - + <InputField type={"file"} name={"file"} invalid={!this.state.isFileValid} + active={this.state.isFileFocused} + onChange={this.handleFileChange} value={this.state.selectedFile} + 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> + <Button variant="green" onClick={this.handleUploadPcap}>upload</Button> + </div> + + <TextField value={uploadOutput} rows={4} readonly small={true}/> </div> </Col> @@ -110,8 +162,6 @@ class PcapPane extends Component { </Container> - - </div> </div> diff --git a/frontend/src/components/panels/PcapPane.scss b/frontend/src/components/panels/PcapPane.scss index 3df87f5..ce28227 100644 --- a/frontend/src/components/panels/PcapPane.scss +++ b/frontend/src/components/panels/PcapPane.scss @@ -38,8 +38,6 @@ padding: 10px; } - - th { background-color: $color-primary-2; border-top: 3px solid $color-primary-3; @@ -50,6 +48,18 @@ padding: 5px; } + .upload-actions { + display: flex; + align-items: flex-end; + margin-bottom: 20px; + } + + .upload-options { + flex: 1; + span { + font-size: 0.9em; + } + } }
\ No newline at end of file |