mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-26 18:18:25 +00:00
Merge pull request #2416 from MatthewShao/mitmweb-options
[WIP] [web] Mitmweb options editor UI
This commit is contained in:
commit
f3231ed758
3
web/src/css/modal.less
Normal file
3
web/src/css/modal.less
Normal file
@ -0,0 +1,3 @@
|
||||
.modal-visible {
|
||||
display: block;
|
||||
}
|
@ -8,13 +8,20 @@ describe('FileMenu Component', () => {
|
||||
let clearFn = jest.fn(),
|
||||
loadFn = jest.fn(),
|
||||
saveFn = jest.fn(),
|
||||
openModalFn = jest.fn(),
|
||||
mockEvent = {
|
||||
preventDefault: jest.fn(),
|
||||
target: { files: ["foo", "bar "] }
|
||||
},
|
||||
createNodeMock = () => { return { click: jest.fn() }},
|
||||
fileMenu = renderer.create(
|
||||
<FileMenu clearFlows={clearFn} loadFlows={loadFn} saveFlows={saveFn}/>, { createNodeMock }),
|
||||
<FileMenu
|
||||
clearFlows={clearFn}
|
||||
loadFlows={loadFn}
|
||||
saveFlows={saveFn}
|
||||
openModal={openModalFn}
|
||||
/>,
|
||||
{ createNodeMock }),
|
||||
tree = fileMenu.toJSON()
|
||||
|
||||
it('should render correctly', () => {
|
||||
@ -42,4 +49,10 @@ describe('FileMenu Component', () => {
|
||||
a.props.onClick(mockEvent)
|
||||
expect(saveFn).toBeCalled()
|
||||
})
|
||||
|
||||
it('should open optionModal', () => {
|
||||
let a = ul.children[3].children[1]
|
||||
a.props.onClick(mockEvent)
|
||||
expect(openModalFn).toBeCalled()
|
||||
})
|
||||
})
|
||||
|
@ -60,6 +60,19 @@ exports[`FileMenu Component should render correctly 1`] = `
|
||||
Save...
|
||||
</a>
|
||||
|
||||
</li>
|
||||
<li>
|
||||
|
||||
<a
|
||||
href="#"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<i
|
||||
className="fa fa-fw fa-cog"
|
||||
/>
|
||||
Options
|
||||
</a>
|
||||
|
||||
</li>
|
||||
<li>
|
||||
|
||||
|
30
web/src/js/__tests__/components/Modal/ModalSpec.js
Normal file
30
web/src/js/__tests__/components/Modal/ModalSpec.js
Normal file
@ -0,0 +1,30 @@
|
||||
import React from 'react'
|
||||
import renderer from 'react-test-renderer'
|
||||
import Modal from '../../../components/Modal/Modal'
|
||||
import { Provider } from 'react-redux'
|
||||
import { TStore } from '../../ducks/tutils'
|
||||
|
||||
describe('Modal Component', () => {
|
||||
let store = TStore()
|
||||
|
||||
it('should render correctly', () => {
|
||||
// hide modal by default
|
||||
let provider = renderer.create(
|
||||
<Provider store={store}>
|
||||
<Modal/>
|
||||
</Provider>
|
||||
),
|
||||
tree = provider.toJSON()
|
||||
expect(tree).toMatchSnapshot()
|
||||
|
||||
// option modal show up
|
||||
store.getState().ui.modal.activeModal = 'OptionModal'
|
||||
provider = renderer.create(
|
||||
<Provider store={store}>
|
||||
<Modal/>
|
||||
</Provider>
|
||||
)
|
||||
tree = provider.toJSON()
|
||||
expect(tree).toMatchSnapshot()
|
||||
})
|
||||
})
|
@ -0,0 +1,66 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Modal Component should render correctly 1`] = `<div />`;
|
||||
|
||||
exports[`Modal Component should render correctly 2`] = `
|
||||
<div>
|
||||
<div
|
||||
className="modal-backdrop fade in"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="options"
|
||||
className="modal modal-visible"
|
||||
id="optionsModal"
|
||||
role="dialog"
|
||||
tabIndex="-1"
|
||||
>
|
||||
<div
|
||||
className="modal-dialog modal-lg"
|
||||
role="document"
|
||||
>
|
||||
<div
|
||||
className="modal-content"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="modal-header"
|
||||
>
|
||||
<button
|
||||
className="close"
|
||||
data-dismiss="modal"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
className="fa fa-fw fa-times"
|
||||
/>
|
||||
</button>
|
||||
<div
|
||||
className="modal-title"
|
||||
>
|
||||
<h4>
|
||||
Options
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="modal-body"
|
||||
>
|
||||
...
|
||||
</div>
|
||||
<div
|
||||
className="modal-footer"
|
||||
>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
type="button"
|
||||
>
|
||||
Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
25
web/src/js/__tests__/ducks/optionsSpec.js
Normal file
25
web/src/js/__tests__/ducks/optionsSpec.js
Normal file
@ -0,0 +1,25 @@
|
||||
jest.mock('../../utils')
|
||||
|
||||
import reduceOptions, * as OptionsActions from '../../ducks/options'
|
||||
|
||||
describe('option reducer', () => {
|
||||
it('should return initial state', () => {
|
||||
expect(reduceOptions(undefined, {})).toEqual({})
|
||||
})
|
||||
|
||||
it('should handle receive action', () => {
|
||||
let action = { type: OptionsActions.RECEIVE, data: 'foo' }
|
||||
expect(reduceOptions(undefined, action)).toEqual('foo')
|
||||
})
|
||||
|
||||
it('should handle update action', () => {
|
||||
let action = {type: OptionsActions.UPDATE, data: {id: 1} }
|
||||
expect(reduceOptions(undefined, action)).toEqual({id: 1})
|
||||
})
|
||||
})
|
||||
|
||||
describe('option actions', () => {
|
||||
it('should be possible to update option', () => {
|
||||
expect(reduceOptions(undefined, OptionsActions.update())).toEqual({})
|
||||
})
|
||||
})
|
@ -32,6 +32,9 @@ export function TStore(){
|
||||
},
|
||||
header: {
|
||||
tab: 'Start'
|
||||
},
|
||||
modal: {
|
||||
activeModal: undefined
|
||||
}
|
||||
},
|
||||
settings: {
|
||||
@ -47,7 +50,8 @@ export function TStore(){
|
||||
sort: {
|
||||
desc: true,
|
||||
column: 'PathColumn'
|
||||
}
|
||||
},
|
||||
view: [ tflow ]
|
||||
},
|
||||
connection: {
|
||||
state: ConnectionState.ESTABLISHED
|
||||
|
25
web/src/js/__tests__/ducks/ui/modalSpec.js
Normal file
25
web/src/js/__tests__/ducks/ui/modalSpec.js
Normal file
@ -0,0 +1,25 @@
|
||||
import reduceModal, * as ModalActions from '../../../ducks/ui/modal'
|
||||
|
||||
describe('modal reducer', () => {
|
||||
let state = undefined
|
||||
|
||||
it('should return the initial state', () => {
|
||||
expect(reduceModal(undefined, {})).toEqual(
|
||||
{ activeModal: undefined }
|
||||
)
|
||||
})
|
||||
|
||||
it('should handle setActiveModal action', () => {
|
||||
state = reduceModal(undefined, ModalActions.setActiveModal('foo'))
|
||||
expect(state).toEqual(
|
||||
{ activeModal: 'foo' }
|
||||
)
|
||||
})
|
||||
|
||||
it('should handle hideModal action', () => {
|
||||
state = reduceModal(state, ModalActions.hideModal())
|
||||
expect(state).toEqual(
|
||||
{ activeModal: undefined }
|
||||
)
|
||||
})
|
||||
})
|
@ -4,11 +4,13 @@ import { connect } from 'react-redux'
|
||||
import FileChooser from '../common/FileChooser'
|
||||
import Dropdown, {Divider} from '../common/Dropdown'
|
||||
import * as flowsActions from '../../ducks/flows'
|
||||
import * as modalActions from '../../ducks/ui/modal'
|
||||
|
||||
FileMenu.propTypes = {
|
||||
clearFlows: PropTypes.func.isRequired,
|
||||
loadFlows: PropTypes.func.isRequired,
|
||||
saveFlows: PropTypes.func.isRequired
|
||||
saveFlows: PropTypes.func.isRequired,
|
||||
openModal: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
FileMenu.onNewClick = (e, clearFlows) => {
|
||||
@ -17,7 +19,7 @@ FileMenu.onNewClick = (e, clearFlows) => {
|
||||
clearFlows()
|
||||
}
|
||||
|
||||
export function FileMenu ({clearFlows, loadFlows, saveFlows}) {
|
||||
export function FileMenu ({clearFlows, loadFlows, saveFlows, openModal}) {
|
||||
return (
|
||||
<Dropdown className="pull-left" btnClass="special" text="mitmproxy">
|
||||
<a href="#" onClick={e => FileMenu.onNewClick(e, clearFlows)}>
|
||||
@ -34,6 +36,11 @@ export function FileMenu ({clearFlows, loadFlows, saveFlows}) {
|
||||
Save...
|
||||
</a>
|
||||
|
||||
<a href="#" onClick={e => { e.preventDefault(); openModal(); }}>
|
||||
<i className="fa fa-fw fa-cog"></i>
|
||||
Options
|
||||
</a>
|
||||
|
||||
<Divider/>
|
||||
|
||||
<a href="http://mitm.it/" target="_blank">
|
||||
@ -50,5 +57,6 @@ export default connect(
|
||||
clearFlows: flowsActions.clear,
|
||||
loadFlows: flowsActions.upload,
|
||||
saveFlows: flowsActions.download,
|
||||
openModal: () => modalActions.setActiveModal('OptionModal'),
|
||||
}
|
||||
)(FileMenu)
|
||||
|
24
web/src/js/components/Modal/Modal.jsx
Normal file
24
web/src/js/components/Modal/Modal.jsx
Normal file
@ -0,0 +1,24 @@
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import ModalList from './ModalList'
|
||||
|
||||
class PureModal extends Component {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { activeModal } = this.props
|
||||
const ActiveModal = ModalList.find(m => m.name === activeModal )
|
||||
return(
|
||||
activeModal ? <ActiveModal/> : <div/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
activeModal: state.ui.modal.activeModal
|
||||
})
|
||||
)(PureModal)
|
16
web/src/js/components/Modal/ModalLayout.jsx
Normal file
16
web/src/js/components/Modal/ModalLayout.jsx
Normal file
@ -0,0 +1,16 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function ModalLayout ({ children }) {
|
||||
return (
|
||||
<div>
|
||||
<div className="modal-backdrop fade in"></div>
|
||||
<div className="modal modal-visible" id="optionsModal" tabIndex="-1" role="dialog" aria-labelledby="options">
|
||||
<div className="modal-dialog modal-lg" role="document">
|
||||
<div className="modal-content">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
13
web/src/js/components/Modal/ModalList.jsx
Normal file
13
web/src/js/components/Modal/ModalList.jsx
Normal file
@ -0,0 +1,13 @@
|
||||
import React from 'react'
|
||||
import ModalLayout from './ModalLayout'
|
||||
import OptionContent from './OptionModal'
|
||||
|
||||
function OptionModal() {
|
||||
return (
|
||||
<ModalLayout>
|
||||
<OptionContent/>
|
||||
</ModalLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default [ OptionModal ]
|
45
web/src/js/components/Modal/OptionModal.jsx
Normal file
45
web/src/js/components/Modal/OptionModal.jsx
Normal file
@ -0,0 +1,45 @@
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import * as modalAction from '../../ducks/ui/modal'
|
||||
|
||||
class PureOptionModal extends Component {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.state = { title: 'Options', }
|
||||
}
|
||||
|
||||
render() {
|
||||
const { hideModal } = this.props
|
||||
const { title } = this.state
|
||||
return (
|
||||
<div>
|
||||
<div className="modal-header">
|
||||
<button type="button" className="close" data-dismiss="modal" onClick={() => {
|
||||
hideModal()
|
||||
}}>
|
||||
<i className="fa fa-fw fa-times"></i>
|
||||
</button>
|
||||
<div className="modal-title">
|
||||
<h4>{ title }</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="modal-body">
|
||||
...
|
||||
</div>
|
||||
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-primary">Save Changes</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
|
||||
}),
|
||||
{ hideModal: modalAction.hideModal }
|
||||
)(PureOptionModal)
|
@ -7,6 +7,7 @@ import MainView from './MainView'
|
||||
import Header from './Header'
|
||||
import EventLog from './EventLog'
|
||||
import Footer from './Footer'
|
||||
import Modal from './Modal/Modal'
|
||||
|
||||
class ProxyAppMain extends Component {
|
||||
|
||||
@ -28,6 +29,7 @@ class ProxyAppMain extends Component {
|
||||
<EventLog key="eventlog"/>
|
||||
)}
|
||||
<Footer />
|
||||
<Modal/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
32
web/src/js/ducks/options.js
Normal file
32
web/src/js/ducks/options.js
Normal file
@ -0,0 +1,32 @@
|
||||
import { fetchApi } from '../utils'
|
||||
|
||||
export const RECEIVE = 'OPTIONS_RECEIVE'
|
||||
export const UPDATE = 'OPTIONS_UPDATE'
|
||||
export const REQUEST_UPDATE = 'REQUEST_UPDATE'
|
||||
export const UNKNOWN_CMD = 'OPTIONS_UNKNOWN_CMD'
|
||||
|
||||
const defaultState = {
|
||||
|
||||
}
|
||||
|
||||
export default function reducer(state = defaultState, action) {
|
||||
switch (action.type) {
|
||||
|
||||
case RECEIVE:
|
||||
return action.data
|
||||
|
||||
case UPDATE:
|
||||
return {
|
||||
...state,
|
||||
...action.data,
|
||||
}
|
||||
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
export function update(options) {
|
||||
fetchApi.put('/options', options)
|
||||
return { type: REQUEST_UPDATE }
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
import { combineReducers } from 'redux'
|
||||
import flow from './flow'
|
||||
import header from './header'
|
||||
import modal from './modal'
|
||||
|
||||
// TODO: Just move ducks/ui/* into ducks/?
|
||||
export default combineReducers({
|
||||
flow,
|
||||
header,
|
||||
modal
|
||||
})
|
||||
|
33
web/src/js/ducks/ui/modal.js
Normal file
33
web/src/js/ducks/ui/modal.js
Normal file
@ -0,0 +1,33 @@
|
||||
export const HIDE_MODAL = 'UI_HIDE_MODAL'
|
||||
export const SET_ACTIVE_MODAL = 'UI_SET_ACTIVE_MODAL'
|
||||
|
||||
const defaultState = {
|
||||
activeModal: undefined,
|
||||
}
|
||||
|
||||
export default function reducer(state = defaultState, action){
|
||||
switch (action.type){
|
||||
|
||||
case SET_ACTIVE_MODAL:
|
||||
return {
|
||||
...state,
|
||||
activeModal: action.activeModal,
|
||||
}
|
||||
|
||||
case HIDE_MODAL:
|
||||
return {
|
||||
...state,
|
||||
activeModal: undefined
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
export function setActiveModal(activeModal) {
|
||||
return { type: SET_ACTIVE_MODAL, activeModal }
|
||||
}
|
||||
|
||||
export function hideModal(){
|
||||
return { type: HIDE_MODAL }
|
||||
}
|
Loading…
Reference in New Issue
Block a user