mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2025-01-30 14:58:38 +00:00
Merge pull request #2423 from MatthewShao/mitmweb-options
[web] [WIP] Mitmweb options editor content
This commit is contained in:
commit
062a58f848
@ -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):
|
||||
|
@ -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",
|
||||
|
@ -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):
|
||||
|
@ -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.
|
||||
|
@ -19,3 +19,4 @@ html {
|
||||
@import (less) "footer.less";
|
||||
@import (less) "codemirror.less";
|
||||
@import (less) "contentview.less";
|
||||
@import (less) "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;
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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},
|
||||
|
@ -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())
|
||||
}
|
||||
|
||||
|
119
web/src/js/components/Modal/OptionMaster.jsx
Normal file
119
web/src/js/components/Modal/OptionMaster.jsx
Normal file
@ -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>
|
||||
)
|
||||
}
|
@ -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)
|
||||
|
@ -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,
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user