diff --git a/web/.editorconfig b/web/.editorconfig
new file mode 100644
index 000000000..9acd1b0f4
--- /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 b49de0027..8fa52a008 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(
- {App},
+
+
+
+
+
+
+
+
+ ,
document.getElementById("mitmproxy")
- );
-
-});
+ )
+})
diff --git a/web/src/js/components/MainView.js b/web/src/js/components/MainView.js
new file mode 100644
index 000000000..19ff5e4d9
--- /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 (
+
+
+ {selectedFlow && [
+ ,
+
+ ]}
+
+ )
+ }
+}
+
+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 000000000..33443dcd3
--- /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 (
+
+
+ {React.cloneElement(
+ children,
+ { ref: "view", location, query, updateLocation: this.updateLocation }
+ )}
+ {showEventLog && [
+ ,
+
+ ]}
+
+ )
+ }
+})
+
+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 5915c9fcd..000000000
--- 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 = [
- ,
-
- ]
- }
-
- return (
-
-
- {details}
-
- );
- }
-});
-
-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 e4489e185..000000000
--- 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 ReportEditor
;
- }
-});
-
-
-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 = [
- ,
-
- ];
- } else {
- eventlog = null;
- }
- return (
-
-
- {React.cloneElement(
- this.props.children,
- { ref: "view", location: this.props.location , updateLocation: this.updateLocation, query }
- )}
- {eventlog}
-
- );
- }
-});
-
-const AppContainer = connect(
- state => ({
- showEventLog: state.eventLog.visible
- })
-)(ProxyAppMain);
-
-
-export var App = (
-
-
-
-
-
-
-
-
-);