From 81a0c45c89df2dc94f7d97c4367f0e549495e4d0 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 9 Jun 2016 20:34:57 +0800 Subject: [web] header.js -> Header.js --- web/src/js/components/Header/FileMenu.jsx | 100 ++++++++++++++++++++ web/src/js/components/Header/FilterDocs.jsx | 56 +++++++++++ web/src/js/components/Header/FilterInput.jsx | 133 +++++++++++++++++++++++++++ web/src/js/components/Header/MainMenu.jsx | 73 +++++++++++++++ web/src/js/components/Header/OptionMenu.jsx | 60 ++++++++++++ web/src/js/components/Header/ViewMenu.jsx | 33 +++++++ 6 files changed, 455 insertions(+) create mode 100644 web/src/js/components/Header/FileMenu.jsx create mode 100644 web/src/js/components/Header/FilterDocs.jsx create mode 100644 web/src/js/components/Header/FilterInput.jsx create mode 100644 web/src/js/components/Header/MainMenu.jsx create mode 100644 web/src/js/components/Header/OptionMenu.jsx create mode 100644 web/src/js/components/Header/ViewMenu.jsx (limited to 'web/src/js/components/Header') diff --git a/web/src/js/components/Header/FileMenu.jsx b/web/src/js/components/Header/FileMenu.jsx new file mode 100644 index 00000000..b075b3c8 --- /dev/null +++ b/web/src/js/components/Header/FileMenu.jsx @@ -0,0 +1,100 @@ +import React, { Component } from 'react' +import classnames from 'classnames' +import { FlowActions } from '../../actions.js' + +export default class FileMenu extends Component { + + constructor(props, context) { + super(props, context) + this.state = { show: false } + + this.close = this.close.bind(this) + this.onFileClick = this.onFileClick.bind(this) + this.onNewClick = this.onNewClick.bind(this) + this.onOpenClick = this.onOpenClick.bind(this) + this.onOpenFile = this.onOpenFile.bind(this) + this.onSaveClick = this.onSaveClick.bind(this) + } + + close() { + this.setState({ show: false }) + document.removeEventListener('click', this.close) + } + + onFileClick(e) { + e.preventDefault() + + if (this.state.show) { + return + } + + document.addEventListener('click', this.close) + this.setState({ show: true }) + } + + onNewClick(e) { + e.preventDefault() + if (confirm('Delete all flows?')) { + FlowActions.clear() + } + } + + onOpenClick(e) { + e.preventDefault() + this.fileInput.click() + } + + onOpenFile(e) { + e.preventDefault() + if (e.target.files.length > 0) { + FlowActions.upload(e.target.files[0]) + this.fileInput.value = '' + } + } + + onSaveClick(e) { + e.preventDefault() + FlowActions.download() + } + + render() { + return ( +
+ mitmproxy + +
+ ) + } +} diff --git a/web/src/js/components/Header/FilterDocs.jsx b/web/src/js/components/Header/FilterDocs.jsx new file mode 100644 index 00000000..efb4818c --- /dev/null +++ b/web/src/js/components/Header/FilterDocs.jsx @@ -0,0 +1,56 @@ +import React, { Component } from 'react' +import $ from 'jquery' + +export default class FilterDocs extends Component { + + // @todo move to redux + + static xhr = null + static doc = null + + constructor(props, context) { + super(props, context) + this.state = { doc: FilterDocs.doc } + } + + componentWillMount() { + if (!FilterDocs.xhr) { + FilterDocs.xhr = $.getJSON('/filter-help') + FilterDocs.xhr.fail(() => { + FilterDocs.xhr = null + }) + } + if (!this.state.doc) { + FilterDocs.xhr.done(doc => { + FilterDocs.doc = doc + this.setState({ doc }) + }) + } + } + + render() { + const { doc } = this.state + return !doc ? ( + + ) : ( + + + {doc.commands.map(cmd => ( + + + + + ))} + + + + +
{cmd[0].replace(' ', '\u00a0')}{cmd[1]}
+ + +   mitmproxy docs +
+ ) + } +} diff --git a/web/src/js/components/Header/FilterInput.jsx b/web/src/js/components/Header/FilterInput.jsx new file mode 100644 index 00000000..5b49b788 --- /dev/null +++ b/web/src/js/components/Header/FilterInput.jsx @@ -0,0 +1,133 @@ +import React, { PropTypes, Component } from 'react' +import ReactDOM from 'react-dom' +import classnames from 'classnames' +import { Key } from '../../utils.js' +import Filt from '../../filt/filt' +import FilterDocs from './FilterDocs' + +export default class FilterInput extends Component { + + static contextTypes = { + returnFocus: React.PropTypes.func, + } + + constructor(props, context) { + super(props, context) + + // Consider both focus and mouseover for showing/hiding the tooltip, + // because onBlur of the input is triggered before the click on the tooltip + // finalized, hiding the tooltip just as the user clicks on it. + this.state = { value: this.props.value, focus: false, mousefocus: false } + + this.onChange = this.onChange.bind(this) + this.onFocus = this.onFocus.bind(this) + this.onBlur = this.onBlur.bind(this) + this.onKeyDown = this.onKeyDown.bind(this) + this.onMouseEnter = this.onMouseEnter.bind(this) + this.onMouseLeave = this.onMouseLeave.bind(this) + } + + componentWillReceiveProps(nextProps) { + this.setState({ value: nextProps.value }) + } + + isValid(filt) { + try { + const str = filt == null ? this.state.value : filt + if (str) { + Filt.parse(str) + } + return true + } catch (e) { + return false + } + } + + getDesc() { + if (!this.state.value) { + return + } + try { + return Filt.parse(this.state.value).desc + } catch (e) { + return '' + e + } + } + + onChange(e) { + const value = e.target.value + this.setState({ value }) + + // Only propagate valid filters upwards. + if (this.isValid(value)) { + this.props.onChange(value) + } + } + + onFocus() { + this.setState({ focus: true }) + } + + onBlur() { + this.setState({ focus: false }) + } + + onMouseEnter() { + this.setState({ mousefocus: true }) + } + + onMouseLeave() { + this.setState({ mousefocus: false }) + } + + onKeyDown(e) { + if (e.keyCode === Key.ESC || e.keyCode === Key.ENTER) { + this.blur() + // If closed using ESC/ENTER, hide the tooltip. + this.setState({mousefocus: false}) + } + e.stopPropagation() + } + + blur() { + ReactDOM.findDOMNode(this.refs.input).blur() + this.context.returnFocus() + } + + select() { + ReactDOM.findDOMNode(this.refs.input).select() + } + + render() { + const { type, color, placeholder } = this.props + const { value, focus, mousefocus } = this.state + return ( +
+ + + + + {(focus || mousefocus) && ( +
+
+
+ {this.getDesc()} +
+
+ )} +
+ ) + } +} diff --git a/web/src/js/components/Header/MainMenu.jsx b/web/src/js/components/Header/MainMenu.jsx new file mode 100644 index 00000000..86bf961a --- /dev/null +++ b/web/src/js/components/Header/MainMenu.jsx @@ -0,0 +1,73 @@ +import React, { Component, PropTypes } from 'react' +import { SettingsActions } from "../../actions.js" +import FilterInput from './FilterInput' +import { Query } from '../../actions.js' + +export default class MainMenu extends Component { + + static title = 'Start' + static route = 'flows' + + static propTypes = { + settings: React.PropTypes.object.isRequired, + } + + constructor(props, context) { + super(props, context) + this.onSearchChange = this.onSearchChange.bind(this) + this.onHighlightChange = this.onHighlightChange.bind(this) + this.onInterceptChange = this.onInterceptChange.bind(this) + } + + onSearchChange(val) { + this.props.updateLocation(undefined, { [Query.SEARCH]: val }) + } + + onHighlightChange(val) { + this.props.updateLocation(undefined, { [Query.HIGHLIGHT]: val }) + } + + onInterceptChange(val) { + SettingsActions.update({ intercept: val }) + } + + render() { + const { query, settings } = this.props + + const search = query[Query.SEARCH] || '' + const highlight = query[Query.HIGHLIGHT] || '' + const intercept = settings.intercept || '' + + return ( +
+
+ + + +
+
+
+ ) + } +} diff --git a/web/src/js/components/Header/OptionMenu.jsx b/web/src/js/components/Header/OptionMenu.jsx new file mode 100644 index 00000000..6bbf15d5 --- /dev/null +++ b/web/src/js/components/Header/OptionMenu.jsx @@ -0,0 +1,60 @@ +import React, { PropTypes } from 'react' +import { ToggleInputButton, ToggleButton } from '../common.js' +import { SettingsActions } from '../../actions.js' + +OptionMenu.title = "Options" + +OptionMenu.propTypes = { + settings: PropTypes.object.isRequired, +} + +export default function OptionMenu({ settings }) { + // @todo use settings.map + return ( +
+
+ SettingsActions.update({ showhost: !settings.showhost })} + /> + SettingsActions.update({ no_upstream_cert: !settings.no_upstream_cert })} + /> + SettingsActions.update({ rawtcp: !settings.rawtcp })} + /> + SettingsActions.update({ http2: !settings.http2 })} + /> + SettingsActions.update({ anticache: !settings.anticache })} + /> + SettingsActions.update({ anticomp: !settings.anticomp })} + /> + SettingsActions.update({ stickyauth: !settings.stickyauth ? txt : null })} + /> + SettingsActions.update({ stickycookie: !settings.stickycookie ? txt : null })} + /> + SettingsActions.update({ stream: !settings.stream ? txt : null })} + /> +
+
+
+ ) +} diff --git a/web/src/js/components/Header/ViewMenu.jsx b/web/src/js/components/Header/ViewMenu.jsx new file mode 100644 index 00000000..45359a83 --- /dev/null +++ b/web/src/js/components/Header/ViewMenu.jsx @@ -0,0 +1,33 @@ +import React, { PropTypes } from 'react' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' +import { ToggleButton } from '../common.js' +import { toggleEventLogVisibility } from '../../ducks/eventLog' + +ViewMenu.title = 'View' +ViewMenu.route = 'flows' + +ViewMenu.propTypes = { + visible: PropTypes.bool.isRequired, + onToggle: PropTypes.func.isRequired, +} + +function ViewMenu({ visible, onToggle }) { + return ( +
+
+ +
+
+
+ ) +} + +export default connect( + state => ({ + visible: state.eventLog.visible, + }), + dispatch => bindActionCreators({ + onToggle: toggleEventLogVisibility, + }, dispatch) +)(ViewMenu) -- cgit v1.2.3