aboutsummaryrefslogtreecommitdiffstats
path: root/web/src/js
diff options
context:
space:
mode:
authorJason <jason.daurus@gmail.com>2016-06-09 20:34:57 +0800
committerJason <jason.daurus@gmail.com>2016-06-09 20:35:03 +0800
commit81a0c45c89df2dc94f7d97c4367f0e549495e4d0 (patch)
treeb9e824f77a7b15b5f3e1a2ab680e2eec5aabf8c8 /web/src/js
parent6c95635cb809d9261acc317f223ef80ba9c25f20 (diff)
downloadmitmproxy-81a0c45c89df2dc94f7d97c4367f0e549495e4d0.tar.gz
mitmproxy-81a0c45c89df2dc94f7d97c4367f0e549495e4d0.tar.bz2
mitmproxy-81a0c45c89df2dc94f7d97c4367f0e549495e4d0.zip
[web] header.js -> Header.js
Diffstat (limited to 'web/src/js')
-rw-r--r--web/src/js/components/EventLog.jsx2
-rw-r--r--web/src/js/components/Header.js56
-rw-r--r--web/src/js/components/Header/FileMenu.jsx100
-rw-r--r--web/src/js/components/Header/FilterDocs.jsx56
-rw-r--r--web/src/js/components/Header/FilterInput.jsx133
-rw-r--r--web/src/js/components/Header/MainMenu.jsx73
-rw-r--r--web/src/js/components/Header/OptionMenu.jsx60
-rw-r--r--web/src/js/components/Header/ViewMenu.jsx33
-rw-r--r--web/src/js/components/ProxyApp.jsx4
-rw-r--r--web/src/js/components/header.js464
-rw-r--r--web/src/js/components/prompt.js4
11 files changed, 516 insertions, 469 deletions
diff --git a/web/src/js/components/EventLog.jsx b/web/src/js/components/EventLog.jsx
index 3de38954..24b3c2bf 100644
--- a/web/src/js/components/EventLog.jsx
+++ b/web/src/js/components/EventLog.jsx
@@ -19,7 +19,7 @@ function EventLog({ filters, events, onToggleFilter, onClose }) {
Eventlog
<div className="pull-right">
{['debug', 'info', 'web'].map(type => (
- <ToggleButton text={type} checked={filters[type]} onToggle={() => onToggleFilter(type)}/>
+ <ToggleButton key={type} text={type} checked={filters[type]} onToggle={() => onToggleFilter(type)}/>
))}
<i onClick={onClose} className="fa fa-close"></i>
</div>
diff --git a/web/src/js/components/Header.js b/web/src/js/components/Header.js
new file mode 100644
index 00000000..7134f7d9
--- /dev/null
+++ b/web/src/js/components/Header.js
@@ -0,0 +1,56 @@
+import React, { Component, PropTypes } from 'react'
+import classnames from 'classnames'
+import { toggleEventLogVisibility } from '../ducks/eventLog'
+import MainMenu from './Header/MainMenu'
+import ViewMenu from './Header/ViewMenu'
+import OptionMenu from './Header/OptionMenu'
+import FileMenu from './Header/FileMenu'
+
+export default class Header extends Component {
+
+ static entries = [MainMenu, ViewMenu, OptionMenu]
+
+ static propTypes = {
+ settings: PropTypes.object.isRequired,
+ }
+
+ constructor(props, context) {
+ super(props, context)
+ this.state = { active: Header.entries[0] }
+ }
+
+ handleClick(active, e) {
+ e.preventDefault()
+ this.props.updateLocation(active.route)
+ this.setState({ active })
+ }
+
+ render() {
+ const { active: Active } = this.state
+ const { settings, updateLocation, query } = this.props
+
+ return (
+ <header>
+ <nav className="nav-tabs nav-tabs-lg">
+ <FileMenu/>
+ {Header.entries.map(Entry => (
+ <a key={Entry.title}
+ href="#"
+ className={classnames({ active: Entry === Active })}
+ onClick={e => this.handleClick(Entry, e)}>
+ {Entry.title}
+ </a>
+ ))}
+ </nav>
+ <div className="menu">
+ <Active
+ ref="active"
+ settings={settings}
+ updateLocation={updateLocation}
+ query={query}
+ />
+ </div>
+ </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 (
+ <div className={classnames('dropdown pull-left', { open: this.state.show })}>
+ <a href="#" className="special" onClick={this.onFileClick}>mitmproxy</a>
+ <ul className="dropdown-menu" role="menu">
+ <li>
+ <a href="#" onClick={this.onNewClick}>
+ <i className="fa fa-fw fa-file"></i>
+ New
+ </a>
+ </li>
+ <li>
+ <a href="#" onClick={this.onOpenClick}>
+ <i className="fa fa-fw fa-folder-open"></i>
+ Open...
+ </a>
+ <input
+ ref={ref => this.fileInput = ref}
+ className="hidden"
+ type="file"
+ onChange={this.onOpenFile}
+ />
+ </li>
+ <li>
+ <a href="#" onClick={this.onSaveClick}>
+ <i className="fa fa-fw fa-floppy-o"></i>
+ Save...
+ </a>
+ </li>
+ <li role="presentation" className="divider"></li>
+ <li>
+ <a href="http://mitm.it/" target="_blank">
+ <i className="fa fa-fw fa-external-link"></i>
+ Install Certificates...
+ </a>
+ </li>
+ </ul>
+ </div>
+ )
+ }
+}
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 ? (
+ <i className="fa fa-spinner fa-spin"></i>
+ ) : (
+ <table className="table table-condensed">
+ <tbody>
+ {doc.commands.map(cmd => (
+ <tr key={cmd[1]}>
+ <td>{cmd[0].replace(' ', '\u00a0')}</td>
+ <td>{cmd[1]}</td>
+ </tr>
+ ))}
+ <tr key="docs-link">
+ <td colSpan="2">
+ <a href="http://docs.mitmproxy.org/en/stable/features/filters.html"
+ target="_blank">
+ <i className="fa fa-external-link"></i>
+ &nbsp mitmproxy docs</a>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ )
+ }
+}
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 <FilterDocs/>
+ }
+ 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 (
+ <div className={classnames('filter-input input-group', { 'has-error': !this.isValid() })}>
+ <span className="input-group-addon">
+ <i className={'fa fa-fw fa-' + type} style={{ color }}></i>
+ </span>
+ <input
+ type="text"
+ ref="input"
+ placeholder={placeholder}
+ className="form-control"
+ value={value}
+ onChange={this.onChange}
+ onFocus={this.onFocus}
+ onBlur={this.onBlur}
+ onKeyDown={this.onKeyDown}
+ />
+ {(focus || mousefocus) && (
+ <div className="popover bottom"
+ onMouseEnter={this.onMouseEnter}
+ onMouseLeave={this.onMouseLeave}>
+ <div className="arrow"></div>
+ <div className="popover-content">
+ {this.getDesc()}
+ </div>
+ </div>
+ )}
+ </div>
+ )
+ }
+}
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 (
+ <div>
+ <div className="menu-row">
+ <FilterInput
+ ref="search"
+ placeholder="Search"
+ type="search"
+ color="black"
+ value={search}
+ onChange={this.onSearchChange}
+ />
+ <FilterInput
+ ref="highlight"
+ placeholder="Highlight"
+ type="tag"
+ color="hsl(48, 100%, 50%)"
+ value={highlight}
+ onChange={this.onHighlightChange}
+ />
+ <FilterInput
+ ref="intercept"
+ placeholder="Intercept"
+ type="pause"
+ color="hsl(208, 56%, 53%)"
+ value={intercept}
+ onChange={this.onInterceptChange}
+ />
+ </div>
+ <div className="clearfix"></div>
+ </div>
+ )
+ }
+}
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 (
+ <div>
+ <div className="menu-row">
+ <ToggleButton text="showhost"
+ checked={settings.showhost}
+ onToggle={() => SettingsActions.update({ showhost: !settings.showhost })}
+ />
+ <ToggleButton text="no_upstream_cert"
+ checked={settings.no_upstream_cert}
+ onToggle={() => SettingsActions.update({ no_upstream_cert: !settings.no_upstream_cert })}
+ />
+ <ToggleButton text="rawtcp"
+ checked={settings.rawtcp}
+ onToggle={() => SettingsActions.update({ rawtcp: !settings.rawtcp })}
+ />
+ <ToggleButton text="http2"
+ checked={settings.http2}
+ onToggle={() => SettingsActions.update({ http2: !settings.http2 })}
+ />
+ <ToggleButton text="anticache"
+ checked={settings.anticache}
+ onToggle={() => SettingsActions.update({ anticache: !settings.anticache })}
+ />
+ <ToggleButton text="anticomp"
+ checked={settings.anticomp}
+ onToggle={() => SettingsActions.update({ anticomp: !settings.anticomp })}
+ />
+ <ToggleInputButton name="stickyauth" placeholder="Sticky auth filter"
+ checked={!!settings.stickyauth}
+ txt={settings.stickyauth || ''}
+ onToggleChanged={txt => SettingsActions.update({ stickyauth: !settings.stickyauth ? txt : null })}
+ />
+ <ToggleInputButton name="stickycookie" placeholder="Sticky cookie filter"
+ checked={!!settings.stickycookie}
+ txt={settings.stickycookie || ''}
+ onToggleChanged={txt => SettingsActions.update({ stickycookie: !settings.stickycookie ? txt : null })}
+ />
+ <ToggleInputButton name="stream" placeholder="stream..."
+ checked={!!settings.stream}
+ txt={settings.stream || ''}
+ inputType="number"
+ onToggleChanged={txt => SettingsActions.update({ stream: !settings.stream ? txt : null })}
+ />
+ </div>
+ <div className="clearfix"/>
+ </div>
+ )
+}
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 (
+ <div>
+ <div className="menu-row">
+ <ToggleButton text="Show Event Log" checked={visible} onToggle={onToggle} />
+ </div>
+ <div className="clearfix"></div>
+ </div>
+ )
+}
+
+export default connect(
+ state => ({
+ visible: state.eventLog.visible,
+ }),
+ dispatch => bindActionCreators({
+ onToggle: toggleEventLogVisibility,
+ }, dispatch)
+)(ViewMenu)
diff --git a/web/src/js/components/ProxyApp.jsx b/web/src/js/components/ProxyApp.jsx
index bab8183d..81272268 100644
--- a/web/src/js/components/ProxyApp.jsx
+++ b/web/src/js/components/ProxyApp.jsx
@@ -4,7 +4,7 @@ import _ from "lodash"
import { connect } from 'react-redux'
import { Splitter } from "./common.js"
-import { Header, MainMenu } from "./header.js"
+import Header from "./Header"
import EventLog from "./EventLog"
import Footer from "./Footer"
import { SettingsStore } from "../store/store.js"
@@ -134,7 +134,7 @@ class ProxyAppMain extends Component {
if (name) {
const headerComponent = this.refs.header
- headerComponent.setState({ active: MainMenu }, () => {
+ headerComponent.setState({ active: Header.entries.MainMenu }, () => {
headerComponent.refs.active.refs[name].select()
})
}
diff --git a/web/src/js/components/header.js b/web/src/js/components/header.js
deleted file mode 100644
index ebd77f91..00000000
--- a/web/src/js/components/header.js
+++ /dev/null
@@ -1,464 +0,0 @@
-import React from "react";
-import ReactDOM from 'react-dom';
-import { bindActionCreators } from 'redux'
-import $ from "jquery";
-import {connect} from 'react-redux'
-
-import Filt from "../filt/filt.js";
-import {Key} from "../utils.js";
-import {ToggleInputButton, ToggleButton} from "./common.js";
-import {SettingsActions, FlowActions} from "../actions.js";
-import {Query} from "../actions.js";
-import {SettingsState} from "./common.js";
-import { toggleEventLogVisibility } from '../ducks/eventLog'
-
-const ToggleEventLog = connect(
- state => ({
- checked: state.eventLog.visible
- }),
- dispatch => bindActionCreators({
- onToggle: toggleEventLogVisibility,
- }, dispatch)
-)(ToggleButton)
-
-var FilterDocs = React.createClass({
- statics: {
- xhr: false,
- doc: false
- },
- componentWillMount: function () {
- if (!FilterDocs.doc) {
- FilterDocs.xhr = $.getJSON("/filter-help").done(function (doc) {
- FilterDocs.doc = doc;
- FilterDocs.xhr = false;
- });
- }
- if (FilterDocs.xhr) {
- FilterDocs.xhr.done(function () {
- this.forceUpdate();
- }.bind(this));
- }
- },
- render: function () {
- if (!FilterDocs.doc) {
- return <i className="fa fa-spinner fa-spin"></i>;
- } else {
- var commands = FilterDocs.doc.commands.map(function (c) {
- return <tr key={c[1]}>
- <td>{c[0].replace(" ", '\u00a0')}</td>
- <td>{c[1]}</td>
- </tr>;
- });
- commands.push(<tr key="docs-link">
- <td colSpan="2">
- <a href="http://docs.mitmproxy.org/en/stable/features/filters.html"
- target="_blank">
- <i className="fa fa-external-link"></i>
- &nbsp; mitmproxy docs</a>
- </td>
- </tr>);
- return <table className="table table-condensed">
- <tbody>{commands}</tbody>
- </table>;
- }
- }
-});
-var FilterInput = React.createClass({
- contextTypes: {
- returnFocus: React.PropTypes.func
- },
- getInitialState: function () {
- // 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.
- return {
- value: this.props.value,
- focus: false,
- mousefocus: false
- };
- },
- componentWillReceiveProps: function (nextProps) {
- this.setState({value: nextProps.value});
- },
- onChange: function (e) {
- var nextValue = e.target.value;
- this.setState({
- value: nextValue
- });
- // Only propagate valid filters upwards.
- if (this.isValid(nextValue)) {
- this.props.onChange(nextValue);
- }
- },
- isValid: function (filt) {
- try {
- var str = filt || this.state.value;
- if(str){
- Filt.parse(filt || this.state.value);
- }
- return true;
- } catch (e) {
- return false;
- }
- },
- getDesc: function () {
- if(this.state.value) {
- try {
- return Filt.parse(this.state.value).desc;
- } catch (e) {
- return "" + e;
- }
- }
- return <FilterDocs/>;
- },
- onFocus: function () {
- this.setState({focus: true});
- },
- onBlur: function () {
- this.setState({focus: false});
- },
- onMouseEnter: function () {
- this.setState({mousefocus: true});
- },
- onMouseLeave: function () {
- this.setState({mousefocus: false});
- },
- onKeyDown: function (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: function () {
- ReactDOM.findDOMNode(this.refs.input).blur();
- this.context.returnFocus();
- },
- select: function () {
- ReactDOM.findDOMNode(this.refs.input).select();
- },
- render: function () {
- var isValid = this.isValid();
- var icon = "fa fa-fw fa-" + this.props.type;
- var groupClassName = "filter-input input-group" + (isValid ? "" : " has-error");
-
- var popover;
- if (this.state.focus || this.state.mousefocus) {
- popover = (
- <div className="popover bottom" onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
- <div className="arrow"></div>
- <div className="popover-content">
- {this.getDesc()}
- </div>
- </div>
- );
- }
-
- return (
- <div className={groupClassName}>
- <span className="input-group-addon">
- <i className={icon} style={{color: this.props.color}}></i>
- </span>
- <input type="text" placeholder={this.props.placeholder} className="form-control"
- ref="input"
- onChange={this.onChange}
- onFocus={this.onFocus}
- onBlur={this.onBlur}
- onKeyDown={this.onKeyDown}
- value={this.state.value}/>
- {popover}
- </div>
- );
- }
-});
-
-export var MainMenu = React.createClass({
- propTypes: {
- settings: React.PropTypes.object.isRequired,
- },
- statics: {
- title: "Start",
- route: "flows"
- },
- onSearchChange: function (val) {
- var d = {};
- d[Query.SEARCH] = val;
- this.props.updateLocation(undefined, d);
- },
- onHighlightChange: function (val) {
- var d = {};
- d[Query.HIGHLIGHT] = val;
- this.props.updateLocation(undefined, d);
- },
- onInterceptChange: function (val) {
- SettingsActions.update({intercept: val});
- },
- render: function () {
- var search = this.props.query[Query.SEARCH] || "";
- var highlight = this.props.query[Query.HIGHLIGHT] || "";
- var intercept = this.props.settings.intercept || "";
-
- return (
- <div>
- <div className="menu-row">
- <FilterInput
- ref="search"
- placeholder="Search"
- type="search"
- color="black"
- value={search}
- onChange={this.onSearchChange} />
- <FilterInput
- ref="highlight"
- placeholder="Highlight"
- type="tag"
- color="hsl(48, 100%, 50%)"
- value={highlight}
- onChange={this.onHighlightChange}/>
- <FilterInput
- ref="intercept"
- placeholder="Intercept"
- type="pause"
- color="hsl(208, 56%, 53%)"
- value={intercept}
- onChange={this.onInterceptChange}/>
- </div>
- <div className="clearfix"></div>
- </div>
- );
- }
-});
-
-
-var ViewMenu = React.createClass({
- statics: {
- title: "View",
- route: "flows"
- },
- render: function () {
- return (
- <div>
- <div className="menu-row">
- <ToggleEventLog text="Show Event Log"/>
- </div>
- <div className="clearfix"></div>
- </div>
- );
- }
-});
-
-export const OptionMenu = (props) => {
- const {mode, intercept, showhost, no_upstream_cert, rawtcp, http2, anticache, anticomp, stickycookie, stickyauth, stream} = props.settings;
- return (
- <div>
- <div className="menu-row">
- <ToggleButton text="showhost"
- checked={showhost}
- onToggle={() => SettingsActions.update({showhost: !showhost})}
- />
- <ToggleButton text="no_upstream_cert"
- checked={no_upstream_cert}
- onToggle={() => SettingsActions.update({no_upstream_cert: !no_upstream_cert})}
- />
- <ToggleButton text="rawtcp"
- checked={rawtcp}
- onToggle={() => SettingsActions.update({rawtcp: !rawtcp})}
- />
- <ToggleButton text="http2"
- checked={http2}
- onToggle={() => SettingsActions.update({http2: !http2})}
- />
- <ToggleButton text="anticache"
- checked={anticache}
- onToggle={() => SettingsActions.update({anticache: !anticache})}
- />
- <ToggleButton text="anticomp"
- checked={anticomp}
- onToggle={() => SettingsActions.update({anticomp: !anticomp})}
- />
- <ToggleInputButton name="stickyauth" placeholder="Sticky auth filter"
- checked={Boolean(stickyauth)}
- txt={stickyauth || ""}
- onToggleChanged={txt => SettingsActions.update({stickyauth: (!stickyauth ? txt : null)})}
- />
- <ToggleInputButton name="stickycookie" placeholder="Sticky cookie filter"
- checked={Boolean(stickycookie)}
- txt={stickycookie || ""}
- onToggleChanged={txt => SettingsActions.update({stickycookie: (!stickycookie ? txt : null)})}
- />
- <ToggleInputButton name="stream" placeholder="stream..."
- checked={Boolean(stream)}
- txt={stream || ""}
- inputType = "number"
- onToggleChanged={txt => SettingsActions.update({stream: (!stream ? txt : null)})}
- />
- </div>
- <div className="clearfix"/>
- </div>
- );
-};
-OptionMenu.title = "Options";
-
-OptionMenu.propTypes = {
- settings: React.PropTypes.object.isRequired
-};
-
-var ReportsMenu = React.createClass({
- statics: {
- title: "Visualization",
- route: "reports"
- },
- render: function () {
- return <div>Reports Menu</div>;
- }
-});
-
-var FileMenu = React.createClass({
- getInitialState: function () {
- return {
- showFileMenu: false
- };
- },
- handleFileClick: function (e) {
- e.preventDefault();
- if (!this.state.showFileMenu) {
- var close = function () {
- this.setState({showFileMenu: false});
- document.removeEventListener("click", close);
- }.bind(this);
- document.addEventListener("click", close);
-
- this.setState({
- showFileMenu: true
- });
- }
- },
- handleNewClick: function (e) {
- e.preventDefault();
- if (confirm("Delete all flows?")) {
- FlowActions.clear();
- }
- },
- handleOpenClick: function (e) {
- this.fileInput.click();
- e.preventDefault();
- },
- handleOpenFile: function (e) {
- if (e.target.files.length > 0) {
- FlowActions.upload(e.target.files[0]);
- this.fileInput.value = "";
- }
- e.preventDefault();
- },
- handleSaveClick: function (e) {
- e.preventDefault();
- FlowActions.download();
- },
- handleShutdownClick: function (e) {
- e.preventDefault();
- console.error("unimplemented: handleShutdownClick");
- },
- render: function () {
- var fileMenuClass = "dropdown pull-left" + (this.state.showFileMenu ? " open" : "");
-
- return (
- <div className={fileMenuClass}>
- <a href="#" className="special" onClick={this.handleFileClick}> mitmproxy </a>
- <ul className="dropdown-menu" role="menu">
- <li>
- <a href="#" onClick={this.handleNewClick}>
- <i className="fa fa-fw fa-file"></i>
- New
- </a>
- </li>
- <li>
- <a href="#" onClick={this.handleOpenClick}>
- <i className="fa fa-fw fa-folder-open"></i>
- Open...
- </a>
- <input ref={(ref) => this.fileInput = ref} className="hidden" type="file" onChange={this.handleOpenFile}/>
-
- </li>
- <li>
- <a href="#" onClick={this.handleSaveClick}>
- <i className="fa fa-fw fa-floppy-o"></i>
- Save...
- </a>
- </li>
- <li role="presentation" className="divider"></li>
- <li>
- <a href="http://mitm.it/" target="_blank">
- <i className="fa fa-fw fa-external-link"></i>
- Install Certificates...
- </a>
- </li>
- {/*
- <li role="presentation" className="divider"></li>
- <li>
- <a href="#" onClick={this.handleShutdownClick}>
- <i className="fa fa-fw fa-plug"></i>
- Shutdown
- </a>
- </li>
- */}
- </ul>
- </div>
- );
- }
-});
-
-
-var header_entries = [MainMenu, ViewMenu, OptionMenu /*, ReportsMenu */];
-
-
-export var Header = React.createClass({
- propTypes: {
- settings: React.PropTypes.object.isRequired,
- },
- getInitialState: function () {
- return {
- active: header_entries[0]
- };
- },
- handleClick: function (active, e) {
- e.preventDefault();
- this.props.updateLocation(active.route);
- this.setState({active: active});
- },
- render: function () {
- var header = header_entries.map(function (entry, i) {
- var className;
- if (entry === this.state.active) {
- className = "active";
- } else {
- className = "";
- }
- return (
- <a key={i}
- href="#"
- className={className}
- onClick={this.handleClick.bind(this, entry)}>
- {entry.title}
- </a>
- );
- }.bind(this));
-
- return (
- <header>
- <nav className="nav-tabs nav-tabs-lg">
- <FileMenu/>
- {header}
- </nav>
- <div className="menu">
- <this.state.active
- ref="active"
- settings={this.props.settings}
- updateLocation={this.props.updateLocation}
- query={this.props.query}
- />
- </div>
- </header>
- );
- }
-});
diff --git a/web/src/js/components/prompt.js b/web/src/js/components/prompt.js
index e324f7d4..5ab26b82 100644
--- a/web/src/js/components/prompt.js
+++ b/web/src/js/components/prompt.js
@@ -42,7 +42,7 @@ var Prompt = React.createClass({
var opts = [];
var keyTaken = function (k) {
- return _.includes(_.pluck(opts, "key"), k);
+ return _.includes(_.map(opts, "key"), k);
};
for (var i = 0; i < this.props.options.length; i++) {
@@ -99,4 +99,4 @@ var Prompt = React.createClass({
}
});
-export default Prompt; \ No newline at end of file
+export default Prompt;