diff options
| author | Jason <jason.daurus@gmail.com> | 2016-06-09 13:35:41 +0800 | 
|---|---|---|
| committer | Jason <jason.daurus@gmail.com> | 2016-06-09 13:35:41 +0800 | 
| commit | 851bb4bf68c20f22c195a4397dacb8cdfdb65fba (patch) | |
| tree | 7ad37c9abd3c7170b785e9dac57324bd79673583 | |
| parent | 7707d096d254a8ce7e1079efeb7584d3cc04bf65 (diff) | |
| download | mitmproxy-851bb4bf68c20f22c195a4397dacb8cdfdb65fba.tar.gz mitmproxy-851bb4bf68c20f22c195a4397dacb8cdfdb65fba.tar.bz2 mitmproxy-851bb4bf68c20f22c195a4397dacb8cdfdb65fba.zip  | |
[web] rewrite ProxyApp and MainView with es6
| -rw-r--r-- | web/.editorconfig | 5 | ||||
| -rw-r--r-- | web/src/js/app.js | 44 | ||||
| -rw-r--r-- | web/src/js/components/MainView.js | 191 | ||||
| -rw-r--r-- | web/src/js/components/ProxyApp.js | 163 | ||||
| -rw-r--r-- | web/src/js/components/mainview.js | 184 | ||||
| -rw-r--r-- | web/src/js/components/proxyapp.js | 154 | 
6 files changed, 385 insertions, 356 deletions
diff --git a/web/.editorconfig b/web/.editorconfig new file mode 100644 index 00000000..9acd1b0f --- /dev/null +++ b/web/.editorconfig @@ -0,0 +1,5 @@ +[*] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/web/src/js/app.js b/web/src/js/app.js index b49de002..8fa52a00 100644 --- a/web/src/js/app.js +++ b/web/src/js/app.js @@ -1,33 +1,41 @@  import React from "react" -import {render} from 'react-dom' -import {applyMiddleware, createStore} from 'redux' -import {Provider} from 'react-redux' +import { render } from 'react-dom' +import { applyMiddleware, createStore } from 'redux' +import { Provider } from 'react-redux'  import createLogger from 'redux-logger'  import thunkMiddleware from 'redux-thunk' - +import { Route, Router as ReactRouter, hashHistory, Redirect } from "react-router"  import Connection from "./connection" -import {App} from "./components/proxyapp.js" -import rootReducer from './ducks/index'; -import {addLogEntry} from "./ducks/eventLog"; +import ProxyApp from "./components/ProxyApp" +import MainView from './components/MainView' +import rootReducer from './ducks/index' +import { addLogEntry } from "./ducks/eventLog"  // logger must be last -const logger = createLogger();  const store = createStore(      rootReducer, -    applyMiddleware(thunkMiddleware, logger) -); +    applyMiddleware(thunkMiddleware, createLogger()) +) -window.onerror = function (msg) { -    store.dispatch(addLogEntry(msg)); -}; +window.addEventListener('error', msg => { +    store.dispatch(addLogEntry(msg)) +}) +// @todo remove this  document.addEventListener('DOMContentLoaded', () => { -    window.ws = new Connection("/updates", store.dispatch); +    window.ws = new Connection("/updates", store.dispatch)      render( -        <Provider store={store}>{App}</Provider>, +        <Provider store={store}> +            <ReactRouter history={hashHistory}> +                <Redirect from="/" to="/flows" /> +                <Route path="/" component={ProxyApp}> +                    <Route path="flows" component={MainView}/> +                    <Route path="flows/:flowId/:detailTab" component={MainView}/> +                </Route> +            </ReactRouter> +        </Provider>,          document.getElementById("mitmproxy") -    ); - -}); +    ) +}) diff --git a/web/src/js/components/MainView.js b/web/src/js/components/MainView.js new file mode 100644 index 00000000..19ff5e4d --- /dev/null +++ b/web/src/js/components/MainView.js @@ -0,0 +1,191 @@ +import React, { Component } from "react" + +import { FlowActions } from "../actions.js" +import { Query } from "../actions.js" +import { Key } from "../utils.js" +import { Splitter } from "./common.js" +import FlowTable from "./flowtable.js" +import FlowView from "./flowview/index.js" +import { connect } from 'react-redux' +import { selectFlow, setFilter, setHighlight } from "../ducks/flows" + +class MainView extends Component { + +    /** +     * @todo move to actions +     * @todo replace with mapStateToProps +     */ +    componentWillReceiveProps(nextProps) { +        // Update redux store with route changes +        if (nextProps.routeParams.flowId !== (nextProps.selectedFlow || {}).id) { +            this.props.selectFlow(nextProps.routeParams.flowId) +        } +        if (nextProps.location.query[Query.SEARCH] !== nextProps.filter) { +            this.props.setFilter(nextProps.location.query[Query.SEARCH], false) +        } +        if (nextProps.location.query[Query.HIGHLIGHT] !== nextProps.highlight) { +            this.props.setHighlight(nextProps.location.query[Query.HIGHLIGHT], false) +        } +    } + +    /** +     * @todo move to actions +     */ +    selectFlow(flow) { +        if (flow) { +            this.props.updateLocation(`/flows/${flow.id}/${this.props.routeParams.detailTab || "request"}`) +        } else { +            this.props.updateLocation("/flows") +        } +    } + +    /** +     * @todo move to actions +     */ +    selectFlowRelative(shift) { +        const { flows, routeParams, selectedFlow } = this.props +        let index = 0 +        if (!routeParams.flowId) { +            if (shift < 0) { +                index = flows.length - 1 +            } +        } else { +            index = Math.min( +                Math.max(0, flows.indexOf(selectedFlow) + shift), +                flows.length - 1 +            ) +        } +        this.selectFlow(flows[index]) +    } + +    /** +     * @todo move to actions +     */ +    onMainKeyDown(e) { +        var flow = this.props.selectedFlow +        if (e.ctrlKey) { +            return +        } +        switch (e.keyCode) { +            case Key.K: +            case Key.UP: +                this.selectFlowRelative(-1) +                break +            case Key.J: +            case Key.DOWN: +                this.selectFlowRelative(+1) +                break +            case Key.SPACE: +            case Key.PAGE_DOWN: +                this.selectFlowRelative(+10) +                break +            case Key.PAGE_UP: +                this.selectFlowRelative(-10) +                break +            case Key.END: +                this.selectFlowRelative(+1e10) +                break +            case Key.HOME: +                this.selectFlowRelative(-1e10) +                break +            case Key.ESC: +                this.selectFlow(null) +                break +            case Key.H: +            case Key.LEFT: +                if (this.refs.flowDetails) { +                    this.refs.flowDetails.nextTab(-1) +                } +                break +            case Key.L: +            case Key.TAB: +            case Key.RIGHT: +                if (this.refs.flowDetails) { +                    this.refs.flowDetails.nextTab(+1) +                } +                break +            case Key.C: +                if (e.shiftKey) { +                    FlowActions.clear() +                } +                break +            case Key.D: +                if (flow) { +                    if (e.shiftKey) { +                        FlowActions.duplicate(flow) +                    } else { +                        FlowActions.delete(flow) +                    } +                } +                break +            case Key.A: +                if (e.shiftKey) { +                    FlowActions.accept_all() +                } else if (flow && flow.intercepted) { +                    FlowActions.accept(flow) +                } +                break +            case Key.R: +                if (!e.shiftKey && flow) { +                    FlowActions.replay(flow) +                } +                break +            case Key.V: +                if (e.shiftKey && flow && flow.modified) { +                    FlowActions.revert(flow) +                } +                break +            case Key.E: +                if (this.refs.flowDetails) { +                    this.refs.flowDetails.promptEdit() +                } +                break +            case Key.SHIFT: +                break +            default: +                console.debug("keydown", e.keyCode) +                return +        } +        e.preventDefault() +    } + +    render() { +        const { selectedFlow } = this.props +        return ( +            <div className="main-view"> +                <FlowTable +                    ref="flowTable" +                    selectFlow={this.selectFlow} +                    selected={selectedFlow} +                /> +                {selectedFlow && [ +                    <Splitter key="splitter"/>, +                    <FlowView +                        key="flowDetails" +                        ref="flowDetails" +                        tab={this.props.routeParams.detailTab} +                        query={this.props.query} +                        updateLocation={this.props.updateLocation} +                        flow={selectedFlow} +                    /> +                ]} +            </div> +        ) +    } +} + +export default connect( +    state => ({ +        flows: state.flows.view, +        filter: state.flows.filter, +        highlight: state.flows.highlight, +        selectedFlow: state.flows.all.byId[state.flows.selected[0]] +    }), +    dispatch => ({ +        selectFlow: flowId => dispatch(selectFlow(flowId)), +        setFilter: filter => dispatch(setFilter(filter)), +        setHighlight: highlight => dispatch(setHighlight(highlight)) +    }), +    undefined, +    { withRef: true } +)(MainView) diff --git a/web/src/js/components/ProxyApp.js b/web/src/js/components/ProxyApp.js new file mode 100644 index 00000000..33443dcd --- /dev/null +++ b/web/src/js/components/ProxyApp.js @@ -0,0 +1,163 @@ +import React, { Component, PropTypes } from "react" +import ReactDOM from "react-dom" +import _ from "lodash" +import { connect } from 'react-redux' + +import { Splitter } from "./common.js" +import { Header, MainMenu } from "./header.js" +import EventLog from "./eventlog.js" +import Footer from "./footer.js" +import { SettingsStore } from "../store/store.js" +import { Key } from "../utils.js" + +class ProxyAppMain extends Component { + +    static childContextTypes = { +        returnFocus: PropTypes.func.isRequired, +        location: PropTypes.object.isRequired, +    } + +    static contextTypes = { +        router: PropTypes.object.isRequired, +    } + +    constructor() { +        this.settingsStore = new SettingsStore() + +        // Default Settings before fetch +        _.extend(this.settingsStore.dict, {}) + +        this.state = { settings: this.settingsStore.dict } +    } + +    /** +     * @todo move to actions +     */ +    updateLocation(pathname, queryUpdate) { +        if (pathname === undefined) { +            pathname = this.props.location.pathname +        } +        const query = this.props.location.query +        for (const key of Object.keys(queryUpdate || {})) { +            query[i] = queryUpdate[i] || undefined +        } +        this.context.router.replace({pathname, query}) +    } + +    /** +     * @todo pass in with props +     */ +    getQuery() { +        // For whatever reason, react-router always returns the same object, which makes comparing +        // the current props with nextProps impossible. As a workaround, we just clone the query object. +        return _.clone(this.props.location.query) +    } + +    /** +     * @todo remove settings store +     * @todo connect websocket here +     * @todo listen to window's key events +     */ +    componentDidMount() { +        this.focus() +        this.settingsStore.addListener("recalculate", this.onSettingsChange) +    } + +    /** +     * @todo remove settings store +     * @todo disconnect websocket here +     * @todo stop listening to window's key events +     */ +    componentWillUnmount() { +        this.settingsStore.removeListener("recalculate", this.onSettingsChange) +    } + +    /** +     * @todo move to actions +     */ +    onSettingsChange() { +        this.setState({ settings: this.settingsStore.dict }) +    } + +    /** +     * @todo use props +     */ +    getChildContext() { +        return { +            returnFocus: this.focus, +            location: this.props.location +        } +    } + +    /** +     * @todo remove it +     */ +    focus() { +        document.activeElement.blur() +        window.getSelection().removeAllRanges() +        ReactDOM.findDOMNode(this).focus() +    } + +    /** +     * @todo move to actions +     */ +    onKeydown(e) { +        let name = null + +        switch (e.keyCode) { +            case Key.I: +                name = "intercept" +                break +            case Key.L: +                name = "search" +                break +            case Key.H: +                name = "highlight" +                break +            default: +                let main = this.refs.view +                if (this.refs.view.getWrappedInstance) { +                    main = this.refs.view.getWrappedInstance() +                } +                if (main.onMainKeyDown) { +                    main.onMainKeyDown(e) +                } +                return // don't prevent default then +        } + +        if (name) { +            const headerComponent = this.refs.header +            headerComponent.setState({active: MainMenu}, function () { +                headerComponent.refs.active.refs[name].select() +            }) +        } + +        e.preventDefault() +    } + +    render() { +        const { showEventLog, location, children } = this.props +        const { settings } = this.state +        const query = this.getQuery() +        return ( +            <div id="container" tabIndex="0" onKeyDown={this.onKeydown}> +                <Header ref="header" settings={settings} updateLocation={this.updateLocation} query={query} /> +                {React.cloneElement( +                    children, +                    { ref: "view", location, query, updateLocation: this.updateLocation } +                )} +                {showEventLog && [ +                    <Splitter key="splitter" axis="y"/>, +                    <EventLog key="eventlog"/> +                ]} +                <Footer settings={settings}/> +            </div> +        ) +    } +}) + +export default connect( +    state => ({ +        showEventLog: state.eventLog.visible +    }) +)(ProxyAppMain) diff --git a/web/src/js/components/mainview.js b/web/src/js/components/mainview.js deleted file mode 100644 index 5915c9fc..00000000 --- a/web/src/js/components/mainview.js +++ /dev/null @@ -1,184 +0,0 @@ -import React from "react"; - -import {FlowActions} from "../actions.js"; -import {Query} from "../actions.js"; -import {Key} from "../utils.js"; -import {Splitter} from "./common.js" -import FlowTable from "./flowtable.js"; -import FlowView from "./flowview/index.js"; -import {connect} from 'react-redux' -import {selectFlow, setFilter, setHighlight} from "../ducks/flows"; - - -var MainView = React.createClass({ -    componentWillReceiveProps: function (nextProps) { -        // Update redux store with route changes -        if(nextProps.routeParams.flowId !== (nextProps.selectedFlow || {}).id) { -            this.props.selectFlow(nextProps.routeParams.flowId) -        } -        if(nextProps.location.query[Query.SEARCH] !== nextProps.filter) { -            this.props.setFilter(nextProps.location.query[Query.SEARCH], false) -        } -        if (nextProps.location.query[Query.HIGHLIGHT] !== nextProps.highlight) { -            this.props.setHighlight(nextProps.location.query[Query.HIGHLIGHT], false) -        } -    }, -    selectFlow: function (flow) { -        // TODO: This belongs into redux -        if (flow) { -            let tab = this.props.routeParams.detailTab || "request"; -            this.props.updateLocation(`/flows/${flow.id}/${tab}`); -        } else { -            this.props.updateLocation("/flows"); -        } -    }, -    selectFlowRelative: function (shift) { -        // TODO: This belongs into redux -        let flows = this.props.flows, -            index -        if (!this.props.routeParams.flowId) { -            if (shift < 0) { -                index = flows.length - 1 -            } else { -                index = 0 -            } -        } else { -            index = flows.indexOf(this.props.selectedFlow) -            index = Math.min( -                Math.max(0, index + shift), -                flows.length - 1 -            ) -        } -        this.selectFlow(flows[index]) -    }, -    onMainKeyDown: function (e) { -        var flow = this.props.selectedFlow; -        if (e.ctrlKey) { -            return; -        } -        switch (e.keyCode) { -            case Key.K: -            case Key.UP: -                this.selectFlowRelative(-1); -                break; -            case Key.J: -            case Key.DOWN: -                this.selectFlowRelative(+1); -                break; -            case Key.SPACE: -            case Key.PAGE_DOWN: -                this.selectFlowRelative(+10); -                break; -            case Key.PAGE_UP: -                this.selectFlowRelative(-10); -                break; -            case Key.END: -                this.selectFlowRelative(+1e10); -                break; -            case Key.HOME: -                this.selectFlowRelative(-1e10); -                break; -            case Key.ESC: -                this.selectFlow(null); -                break; -            case Key.H: -            case Key.LEFT: -                if (this.refs.flowDetails) { -                    this.refs.flowDetails.nextTab(-1); -                } -                break; -            case Key.L: -            case Key.TAB: -            case Key.RIGHT: -                if (this.refs.flowDetails) { -                    this.refs.flowDetails.nextTab(+1); -                } -                break; -            case Key.C: -                if (e.shiftKey) { -                    FlowActions.clear(); -                } -                break; -            case Key.D: -                if (flow) { -                    if (e.shiftKey) { -                        FlowActions.duplicate(flow); -                    } else { -                        FlowActions.delete(flow); -                    } -                } -                break; -            case Key.A: -                if (e.shiftKey) { -                    FlowActions.accept_all(); -                } else if (flow && flow.intercepted) { -                    FlowActions.accept(flow); -                } -                break; -            case Key.R: -                if (!e.shiftKey && flow) { -                    FlowActions.replay(flow); -                } -                break; -            case Key.V: -                if (e.shiftKey && flow && flow.modified) { -                    FlowActions.revert(flow); -                } -                break; -            case Key.E: -                if (this.refs.flowDetails) { -                    this.refs.flowDetails.promptEdit(); -                } -                break; -            case Key.SHIFT: -                break; -            default: -                console.debug("keydown", e.keyCode); -                return; -        } -        e.preventDefault(); -    }, -    render: function () { - -        var details = null; -        if (this.props.selectedFlow) { -            details = [ -                <Splitter key="splitter"/>, -                <FlowView -                    key="flowDetails" -                    ref="flowDetails" -                    tab={this.props.routeParams.detailTab} -                    query={this.props.query} -                    updateLocation={this.props.updateLocation} -                    flow={this.props.selectedFlow}/> -            ] -        } - -        return ( -            <div className="main-view"> -                <FlowTable ref="flowTable" -                    selectFlow={this.selectFlow} -                    selected={this.props.selectedFlow} /> -                {details} -            </div> -        ); -    } -}); - -const MainViewContainer = connect( -    state => ({ -        flows: state.flows.view, -        filter: state.flows.filter, -        highlight: state.flows.highlight, -        selectedFlow: state.flows.all.byId[state.flows.selected[0]] -    }), -    dispatch => ({ -        selectFlow: flowId => dispatch(selectFlow(flowId)), -        setFilter: filter => dispatch(setFilter(filter)), -        setHighlight: highlight => dispatch(setHighlight(highlight)) -    }), -    undefined, -    {withRef: true} -)(MainView); - -export default MainViewContainer; diff --git a/web/src/js/components/proxyapp.js b/web/src/js/components/proxyapp.js deleted file mode 100644 index e4489e18..00000000 --- a/web/src/js/components/proxyapp.js +++ /dev/null @@ -1,154 +0,0 @@ -import React from "react"; -import ReactDOM from "react-dom"; -import _ from "lodash"; -import {connect} from 'react-redux' -import { Route, Router as ReactRouter, hashHistory, Redirect} from "react-router" - -import {Splitter} from "./common.js" -import MainView from "./mainview.js"; -import Footer from "./footer.js"; -import {Header, MainMenu} from "./header.js"; -import EventLog from "./eventlog.js" -import {SettingsStore} from "../store/store.js"; -import {Key} from "../utils.js"; - - -//TODO: Move out of here, just a stub. -var Reports = React.createClass({ -    render: function () { -        return <div>ReportEditor</div>; -    } -}); - - -var ProxyAppMain = React.createClass({ -    childContextTypes: { -        returnFocus: React.PropTypes.func.isRequired, -        location: React.PropTypes.object.isRequired, -    }, -    contextTypes: { -        router: React.PropTypes.object.isRequired -    }, -    updateLocation: function (pathname, queryUpdate) { -        if (pathname === undefined) { -            pathname = this.props.location.pathname; -        } -        var query = this.props.location.query; -        if (queryUpdate !== undefined) { -            for (var i in queryUpdate) { -                if (queryUpdate.hasOwnProperty(i)) { -                    query[i] = queryUpdate[i] || undefined; //falsey values shall be removed. -                } -            } -        } -        this.context.router.replace({pathname, query}); -    }, -    getQuery: function () { -        // For whatever reason, react-router always returns the same object, which makes comparing -        // the current props with nextProps impossible. As a workaround, we just clone the query object. -        return _.clone(this.props.location.query); -    }, -    componentDidMount: function () { -        this.focus(); -        this.settingsStore.addListener("recalculate", this.onSettingsChange); -    }, -    componentWillUnmount: function () { -        this.settingsStore.removeListener("recalculate", this.onSettingsChange); -    }, -    onSettingsChange: function () { -        this.setState({ settings: this.settingsStore.dict }); -    }, -    getChildContext: function () { -        return { -            returnFocus: this.focus, -            location: this.props.location -        }; -    }, -    getInitialState: function () { -        var settingsStore = new SettingsStore(); - -        this.settingsStore = settingsStore; -        // Default Settings before fetch -        _.extend(settingsStore.dict, {}); -        return { -            settings: settingsStore.dict, -        }; -    }, -    focus: function () { -        document.activeElement.blur(); -        window.getSelection().removeAllRanges(); -        ReactDOM.findDOMNode(this).focus(); -    }, -    getMainComponent: function () { -        return this.refs.view.getWrappedInstance ? this.refs.view.getWrappedInstance() : this.refs.view; -    }, -    onKeydown: function (e) { - -        var selectFilterInput = function (name) { -            var headerComponent = this.refs.header; -            headerComponent.setState({active: MainMenu}, function () { -                headerComponent.refs.active.refs[name].select(); -            }); -        }.bind(this); - -        switch (e.keyCode) { -            case Key.I: -                selectFilterInput("intercept"); -                break; -            case Key.L: -                selectFilterInput("search"); -                break; -            case Key.H: -                selectFilterInput("highlight"); -                break; -            default: -                var main = this.getMainComponent(); -                if (main.onMainKeyDown) { -                    main.onMainKeyDown(e); -                } -                return; // don't prevent default then -        } -        e.preventDefault(); -    }, -    render: function () { -        var query = this.getQuery(); -        var eventlog; -        if (this.props.showEventLog) { -            eventlog = [ -                <Splitter key="splitter" axis="y"/>, -                <EventLog key="eventlog"/> -            ]; -        } else { -            eventlog = null; -        } -        return ( -            <div id="container" tabIndex="0" onKeyDown={this.onKeydown}> -                <Header ref="header" settings={this.state.settings} updateLocation={this.updateLocation} query={query} /> -                {React.cloneElement( -                    this.props.children, -                    { ref: "view", location: this.props.location , updateLocation: this.updateLocation, query } -                )} -                {eventlog} -                <Footer settings={this.state.settings}/> -            </div> -        ); -    } -}); - -const AppContainer = connect( -    state => ({ -        showEventLog: state.eventLog.visible -    }) -)(ProxyAppMain); - - -export var App = ( -    <ReactRouter history={hashHistory}> -        <Redirect from="/" to="/flows" /> -        <Route path="/" component={AppContainer}> -            <Route path="flows" component={MainView}/> -            <Route path="flows/:flowId/:detailTab" component={MainView}/> -            <Route path="reports" component={Reports}/> -        </Route> -    </ReactRouter> -);  | 
