aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMaximilian Hils <git@maximilianhils.com>2017-07-05 16:25:38 +0200
committerGitHub <noreply@github.com>2017-07-05 16:25:38 +0200
commit062a58f848c80b3a5f499d407396998ba253202c (patch)
tree29af51e332bb8d103a786d2f85dcf31f25516ebf
parent55a20b819e1220024a66bd11275a5c3511f7d295 (diff)
parent37fea267c1d171fb661736b96db62943e7b49791 (diff)
downloadmitmproxy-062a58f848c80b3a5f499d407396998ba253202c.tar.gz
mitmproxy-062a58f848c80b3a5f499d407396998ba253202c.tar.bz2
mitmproxy-062a58f848c80b3a5f499d407396998ba253202c.zip
Merge pull request #2423 from MatthewShao/mitmweb-options
[web] [WIP] Mitmweb options editor content
-rw-r--r--mitmproxy/optmanager.py14
-rw-r--r--mitmproxy/tools/web/master.py10
-rw-r--r--test/mitmproxy/test_optmanager.py1
-rw-r--r--test/mitmproxy/tools/web/test_app.py28
-rw-r--r--web/src/css/app.less1
-rw-r--r--web/src/css/modal.less10
-rw-r--r--web/src/js/__tests__/components/Modal/__snapshots__/ModalSpec.js.snap79
-rw-r--r--web/src/js/__tests__/ducks/tutils.js30
-rw-r--r--web/src/js/backends/websocket.js1
-rw-r--r--web/src/js/components/Modal/OptionMaster.jsx119
-rw-r--r--web/src/js/components/Modal/OptionModal.jsx27
-rw-r--r--web/src/js/ducks/index.js2
12 files changed, 305 insertions, 17 deletions
diff --git a/mitmproxy/optmanager.py b/mitmproxy/optmanager.py
index 68c2f975..e1d74b8e 100644
--- a/mitmproxy/optmanager.py
+++ b/mitmproxy/optmanager.py
@@ -409,26 +409,26 @@ def dump_defaults(opts):
return ruamel.yaml.round_trip_dump(s)
-def dump_dicts(opts):
+def dump_dicts(opts, keys: typing.List[str]=None):
"""
Dumps the options into a list of dict object.
- Return: A list like: [ { name: "anticache", type: "bool", default: false, value: true, help: "help text"} ]
+ Return: A list like: { "anticache": { type: "bool", default: false, value: true, help: "help text"} }
"""
- options_list = []
- for k in sorted(opts.keys()):
+ options_dict = {}
+ keys = keys if keys else opts.keys()
+ for k in sorted(keys):
o = opts._options[k]
t = typecheck.typespec_to_str(o.typespec)
option = {
- 'name': k,
'type': t,
'default': o.default,
'value': o.current(),
'help': o.help,
'choices': o.choices
}
- options_list.append(option)
- return options_list
+ options_dict[k] = option
+ return options_dict
def parse(text):
diff --git a/mitmproxy/tools/web/master.py b/mitmproxy/tools/web/master.py
index c09fe0a2..8c2433ec 100644
--- a/mitmproxy/tools/web/master.py
+++ b/mitmproxy/tools/web/master.py
@@ -5,6 +5,7 @@ import tornado.ioloop
from mitmproxy import addons
from mitmproxy import log
from mitmproxy import master
+from mitmproxy import optmanager
from mitmproxy.addons import eventstore
from mitmproxy.addons import intercept
from mitmproxy.addons import readfile
@@ -29,6 +30,7 @@ class WebMaster(master.Master):
self.events.sig_refresh.connect(self._sig_events_refresh)
self.options.changed.connect(self._sig_options_update)
+ self.options.changed.connect(self._sig_settings_update)
self.addons.add(*addons.default_addons())
self.addons.add(
@@ -86,6 +88,14 @@ class WebMaster(master.Master):
)
def _sig_options_update(self, options, updated):
+ options_dict = optmanager.dump_dicts(options, updated)
+ app.ClientConnection.broadcast(
+ resource="options",
+ cmd="update",
+ data=options_dict
+ )
+
+ def _sig_settings_update(self, options, updated):
app.ClientConnection.broadcast(
resource="settings",
cmd="update",
diff --git a/test/mitmproxy/test_optmanager.py b/test/mitmproxy/test_optmanager.py
index 0c400683..7b4ffb8b 100644
--- a/test/mitmproxy/test_optmanager.py
+++ b/test/mitmproxy/test_optmanager.py
@@ -341,6 +341,7 @@ def test_dump_defaults():
def test_dump_dicts():
o = options.Options()
assert optmanager.dump_dicts(o)
+ assert optmanager.dump_dicts(o, ['http2', 'anticomp'])
class TTypes(optmanager.OptManager):
diff --git a/test/mitmproxy/tools/web/test_app.py b/test/mitmproxy/tools/web/test_app.py
index e6d563e7..bb439b34 100644
--- a/test/mitmproxy/tools/web/test_app.py
+++ b/test/mitmproxy/tools/web/test_app.py
@@ -255,8 +255,8 @@ class TestApp(tornado.testing.AsyncHTTPTestCase):
def test_options(self):
j = json(self.fetch("/options"))
- assert type(j) == list
- assert type(j[0]) == dict
+ assert type(j) == dict
+ assert type(j['anticache']) == dict
def test_option_update(self):
assert self.put_json("/options", {"anticache": True}).code == 200
@@ -275,12 +275,32 @@ class TestApp(tornado.testing.AsyncHTTPTestCase):
ws_client = yield websocket.websocket_connect(ws_url)
self.master.options.anticomp = True
- response = yield ws_client.read_message()
- assert _json.loads(response) == {
+ r1 = yield ws_client.read_message()
+ r2 = yield ws_client.read_message()
+ j1 = _json.loads(r1)
+ j2 = _json.loads(r2)
+ print(j1)
+ response = dict()
+ response[j1['resource']] = j1
+ response[j2['resource']] = j2
+ assert response['settings'] == {
"resource": "settings",
"cmd": "update",
"data": {"anticomp": True},
}
+ assert response['options'] == {
+ "resource": "options",
+ "cmd": "update",
+ "data": {
+ "anticomp": {
+ "value": True,
+ "choices": None,
+ "default": False,
+ "help": "Try to convince servers to send us un-compressed data.",
+ "type": "bool",
+ }
+ }
+ }
ws_client.close()
# trigger on_close by opening a second connection.
diff --git a/web/src/css/app.less b/web/src/css/app.less
index 353e412a..b9b5b310 100644
--- a/web/src/css/app.less
+++ b/web/src/css/app.less
@@ -19,3 +19,4 @@ html {
@import (less) "footer.less";
@import (less) "codemirror.less";
@import (less) "contentview.less";
+@import (less) "modal.less";
diff --git a/web/src/css/modal.less b/web/src/css/modal.less
index b08e309a..8d578f03 100644
--- a/web/src/css/modal.less
+++ b/web/src/css/modal.less
@@ -1,3 +1,13 @@
.modal-visible {
display: block;
}
+
+
+.modal-dialog {
+ overflow-y: initial !important;
+}
+
+.modal-body {
+ max-height: calc(100vh - 20px);
+ overflow-y: auto;
+}
diff --git a/web/src/js/__tests__/components/Modal/__snapshots__/ModalSpec.js.snap b/web/src/js/__tests__/components/Modal/__snapshots__/ModalSpec.js.snap
index 4fe163d1..af587ae4 100644
--- a/web/src/js/__tests__/components/Modal/__snapshots__/ModalSpec.js.snap
+++ b/web/src/js/__tests__/components/Modal/__snapshots__/ModalSpec.js.snap
@@ -46,7 +46,84 @@ exports[`Modal Component should render correctly 2`] = `
<div
className="modal-body"
>
- ...
+ <div
+ className="menu-entry"
+ >
+ <label>
+ booleanOption
+ <input
+ checked={false}
+ onChange={[Function]}
+ title="foo"
+ type="checkbox"
+ />
+ </label>
+ </div>
+ <div
+ className="menu-entry"
+ >
+ <label
+ htmlFor=""
+ >
+ choiceOption
+ <select
+ name="choiceOption"
+ onChange={[Function]}
+ selected="b"
+ title="foo"
+ >
+ <option
+ value="a"
+ >
+
+ a
+
+ </option>
+ <option
+ value="b"
+ >
+
+ b
+
+ </option>
+ <option
+ value="c"
+ >
+
+ c
+
+ </option>
+ </select>
+ </label>
+ </div>
+ <div
+ className="menu-entry"
+ >
+ <label>
+ intOption
+ <input
+ onChange={[Function]}
+ onKeyDown={[Function]}
+ title="foo"
+ type="number"
+ value={1}
+ />
+ </label>
+ </div>
+ <div
+ className="menu-entry"
+ >
+ <label>
+ strOption
+ <input
+ onChange={[Function]}
+ onKeyDown={[Function]}
+ title="foo"
+ type="text"
+ value="str content"
+ />
+ </label>
+ </div>
</div>
<div
className="modal-footer"
diff --git a/web/src/js/__tests__/ducks/tutils.js b/web/src/js/__tests__/ducks/tutils.js
index 9b92e676..a3e9c168 100644
--- a/web/src/js/__tests__/ducks/tutils.js
+++ b/web/src/js/__tests__/ducks/tutils.js
@@ -42,6 +42,36 @@ export function TStore(){
anticache: true,
anticomp: false
},
+ options: {
+ booleanOption: {
+ choices: null,
+ default: false,
+ help: "foo",
+ type: "bool",
+ value: false
+ },
+ strOption: {
+ choices: null,
+ default: null,
+ help: "foo",
+ type: "str",
+ value: "str content"
+ },
+ intOption: {
+ choices: null,
+ default: 0,
+ help: "foo",
+ type: "int",
+ value: 1
+ },
+ choiceOption: {
+ choices: ['a', 'b', 'c'],
+ default: 'a',
+ help: "foo",
+ type: "str",
+ value: "b"
+ },
+ },
flows: {
selected: ["d91165be-ca1f-4612-88a9-c0f8696f3e29"],
byId: {"d91165be-ca1f-4612-88a9-c0f8696f3e29": tflow},
diff --git a/web/src/js/backends/websocket.js b/web/src/js/backends/websocket.js
index 01094ac4..d7e13bb2 100644
--- a/web/src/js/backends/websocket.js
+++ b/web/src/js/backends/websocket.js
@@ -27,6 +27,7 @@ export default class WebsocketBackend {
this.fetchData("settings")
this.fetchData("flows")
this.fetchData("events")
+ this.fetchData("options")
this.store.dispatch(connectionActions.startFetching())
}
diff --git a/web/src/js/components/Modal/OptionMaster.jsx b/web/src/js/components/Modal/OptionMaster.jsx
new file mode 100644
index 00000000..c25dda72
--- /dev/null
+++ b/web/src/js/components/Modal/OptionMaster.jsx
@@ -0,0 +1,119 @@
+import PropTypes from 'prop-types'
+
+PureBooleanOption.PropTypes = {
+ value: PropTypes.bool.isRequired,
+ onChange: PropTypes.func.isRequired,
+}
+
+function PureBooleanOption({ value, onChange, name, help}) {
+ return (
+ <label>
+ { name }
+ <input type="checkbox"
+ checked={value}
+ onChange={onChange}
+ title={help}
+ />
+ </label>
+ )
+}
+
+PureStringOption.PropTypes = {
+ value: PropTypes.string.isRequired,
+ onChange: PropTypes.func.isRequired,
+}
+
+function PureStringOption( { value, onChange, name, help }) {
+ let onKeyDown = (e) => {e.stopPropagation()}
+ return (
+ <label>
+ { name }
+ <input type="text"
+ value={value}
+ onChange={onChange}
+ title={help}
+ onKeyDown={onKeyDown}
+ />
+ </label>
+ )
+}
+
+PureNumberOption.PropTypes = {
+ value: PropTypes.number.isRequired,
+ onChange: PropTypes.func.isRequired,
+}
+
+function PureNumberOption( {value, onChange, name, help }) {
+ let onKeyDown = (e) => {e.stopPropagation()}
+ return (
+ <label>
+ { name }
+ <input type="number"
+ value={value}
+ onChange={onChange}
+ title={help}
+ onKeyDown={onKeyDown}
+ />
+ </label>
+ )
+}
+
+PureChoicesOption.PropTypes = {
+ value: PropTypes.string.isRequired,
+ onChange: PropTypes.func.isRequired,
+}
+
+function PureChoicesOption( { value, onChange, name, help, choices }) {
+ return (
+ <label htmlFor="">
+ { name }
+ <select name={name} onChange={onChange} title={help} selected={value}>
+ { choices.map((choice, index) => (
+ <option key={index} value={choice}> {choice} </option>
+ ))}
+ </select>
+ </label>
+ )
+}
+
+const OptionTypes = {
+ bool: PureBooleanOption,
+ str: PureStringOption,
+ int: PureNumberOption,
+ "optional str": PureStringOption,
+ "sequence of str": PureStringOption,
+}
+
+export default function OptionMaster({option, name, updateOptions, ...props}) {
+ let WrappedComponent = null
+ if (option.choices) {
+ WrappedComponent = PureChoicesOption
+ } else {
+ WrappedComponent = OptionTypes[option.type]
+ }
+
+ let onChange = (e) => {
+ switch (option.type) {
+ case 'bool' :
+ updateOptions({[name]: !option.value})
+ break
+ case 'int':
+ updateOptions({[name]: parseInt(e.target.value)})
+ break
+ default:
+ updateOptions({[name]: e.target.value})
+ }
+ }
+ return (
+ <div className="menu-entry">
+ <WrappedComponent
+ children={props.children}
+ value={option.value}
+ onChange={onChange}
+ name={name}
+ help={option.help}
+ choices={option.choices}
+ />
+ </div>
+ )
+}
diff --git a/web/src/js/components/Modal/OptionModal.jsx b/web/src/js/components/Modal/OptionModal.jsx
index 500495c4..ef3a224a 100644
--- a/web/src/js/components/Modal/OptionModal.jsx
+++ b/web/src/js/components/Modal/OptionModal.jsx
@@ -1,16 +1,18 @@
import React, { Component } from 'react'
import { connect } from 'react-redux'
import * as modalAction from '../../ducks/ui/modal'
+import { update as updateOptions } from '../../ducks/options'
+import Option from './OptionMaster'
class PureOptionModal extends Component {
constructor(props, context) {
super(props, context)
- this.state = { title: 'Options', }
+ this.state = { title: 'Options' }
}
render() {
- const { hideModal } = this.props
+ const { hideModal, options } = this.props
const { title } = this.state
return (
<div>
@@ -26,7 +28,19 @@ class PureOptionModal extends Component {
</div>
<div className="modal-body">
- ...
+ {
+ Object.keys(options).sort()
+ .map((key, index) => {
+ let option = options[key];
+ return (
+ <Option
+ key={index}
+ name={key}
+ updateOptions={updateOptions}
+ option={option}
+ />)
+ })
+ }
</div>
<div className="modal-footer">
@@ -39,7 +53,10 @@ class PureOptionModal extends Component {
export default connect(
state => ({
-
+ options: state.options
}),
- { hideModal: modalAction.hideModal }
+ {
+ hideModal: modalAction.hideModal,
+ updateOptions: updateOptions,
+ }
)(PureOptionModal)
diff --git a/web/src/js/ducks/index.js b/web/src/js/ducks/index.js
index 0f2426ec..be2f2885 100644
--- a/web/src/js/ducks/index.js
+++ b/web/src/js/ducks/index.js
@@ -4,6 +4,7 @@ import flows from "./flows"
import settings from "./settings"
import ui from "./ui/index"
import connection from "./connection"
+import options from './options'
export default combineReducers({
eventLog,
@@ -11,4 +12,5 @@ export default combineReducers({
settings,
connection,
ui,
+ options,
})