diff options
author | Maximilian Hils <git@maximilianhils.com> | 2014-09-15 18:08:26 +0200 |
---|---|---|
committer | Maximilian Hils <git@maximilianhils.com> | 2014-09-15 18:08:26 +0200 |
commit | cbf18320cdbd05197f232da69b3c9a5391735156 (patch) | |
tree | 63b9cf8fd6a8e8fb5be6b2d5a655acf3d33b6229 /web | |
parent | 9f8d2eea64d4611c1e2f7e7043fe6d3ef9a6aa40 (diff) | |
download | mitmproxy-cbf18320cdbd05197f232da69b3c9a5391735156.tar.gz mitmproxy-cbf18320cdbd05197f232da69b3c9a5391735156.tar.bz2 mitmproxy-cbf18320cdbd05197f232da69b3c9a5391735156.zip |
client-side structure
Diffstat (limited to 'web')
-rw-r--r-- | web/gulpfile.js | 19 | ||||
-rw-r--r-- | web/src/css/app.less | 1 | ||||
-rw-r--r-- | web/src/css/eventlog.less | 11 | ||||
-rw-r--r-- | web/src/css/footer.less | 4 | ||||
-rw-r--r-- | web/src/css/header.less | 9 | ||||
-rw-r--r-- | web/src/css/layout.less | 33 | ||||
-rw-r--r-- | web/src/index.html | 3 | ||||
-rw-r--r-- | web/src/js/Connection.es6.js | 33 | ||||
-rw-r--r-- | web/src/js/Dispatcher.es6.js | 36 | ||||
-rw-r--r-- | web/src/js/actions.es6.js | 14 | ||||
-rw-r--r-- | web/src/js/app.js | 5 | ||||
-rw-r--r-- | web/src/js/components/EventLog.react.js | 13 | ||||
-rw-r--r-- | web/src/js/components/Footer.react.js (renamed from web/src/js/footer.react.js) | 2 | ||||
-rw-r--r-- | web/src/js/components/Header.react.js | 88 | ||||
-rw-r--r-- | web/src/js/components/ProxyApp.react.js | 35 | ||||
-rw-r--r-- | web/src/js/components/TrafficTable.react.js | 38 | ||||
-rw-r--r-- | web/src/js/datastructures.es6.js | 105 | ||||
-rw-r--r-- | web/src/js/header.react.js | 72 | ||||
-rw-r--r-- | web/src/js/mitmproxy.react.js | 82 | ||||
-rw-r--r-- | web/src/js/stores/EventLogStore.es6.js | 42 | ||||
-rw-r--r-- | web/src/js/stores/SettingsStore.es6.js | 42 | ||||
-rw-r--r-- | web/src/js/stores/base.es6.js | 26 |
22 files changed, 409 insertions, 304 deletions
diff --git a/web/gulpfile.js b/web/gulpfile.js index bae4955b..68a5e479 100644 --- a/web/gulpfile.js +++ b/web/gulpfile.js @@ -32,10 +32,19 @@ var path = { 'vendor/react-bootstrap/react-bootstrap.js' ], app: [ - 'js/datastructures.es6.js', - 'js/footer.react.js', - 'js/header.react.js', - 'js/mitmproxy.react.js', + 'js/Dispatcher.es6.js', + 'js/actions.es6.js', + 'js/stores/base.es6.js', + 'js/stores/SettingsStore.es6.js', + 'js/stores/EventLogStore.es6.js', + 'js/Connection.es6.js', + 'js/connection.es6.js', + 'js/components/Header.react.js', + 'js/components/TrafficTable.react.js', + 'js/components/EventLog.react.js', + 'js/components/Footer.react.js', + 'js/components/ProxyApp.react.js', + 'js/app.js', ], }, css: { @@ -116,4 +125,4 @@ gulp.task("default", ["dev"], function () { gulp.watch(["src/js/**"], ["scripts-app-dev", "jshint"]); gulp.watch(["src/css/**"], ["styles-app-dev"]); gulp.watch(["src/*.html"], ["html"]); -});
\ No newline at end of file +}); diff --git a/web/src/css/app.less b/web/src/css/app.less index ff3c614c..ce9d9149 100644 --- a/web/src/css/app.less +++ b/web/src/css/app.less @@ -9,5 +9,6 @@ html { @import (less) "layout.less"; @import (less) "header.less"; +@import (less) "eventlog.less"; @import (less) "footer.less"; diff --git a/web/src/css/eventlog.less b/web/src/css/eventlog.less new file mode 100644 index 00000000..0e97832b --- /dev/null +++ b/web/src/css/eventlog.less @@ -0,0 +1,11 @@ +.eventlog { + + flex: 0 0 auto; + + pre { + margin: 0; + border-radius: 0; + height: 200px; + overflow: auto; + } +}
\ No newline at end of file diff --git a/web/src/css/footer.less b/web/src/css/footer.less index 69ab62ce..be7a1d76 100644 --- a/web/src/css/footer.less +++ b/web/src/css/footer.less @@ -1,4 +1,4 @@ footer { - padding: 0 10px; - //text-align: center; + box-shadow: 0 -1px 3px lightgray; + padding: 0px 10px 3px; }
\ No newline at end of file diff --git a/web/src/css/header.less b/web/src/css/header.less index 4f4af121..69a947c5 100644 --- a/web/src/css/header.less +++ b/web/src/css/header.less @@ -42,15 +42,6 @@ header { } } } - - &:before { - content: " "; - } - - &:after { - clear: both; - } - } .menu { diff --git a/web/src/css/layout.less b/web/src/css/layout.less index 7c5f79b9..c8fad204 100644 --- a/web/src/css/layout.less +++ b/web/src/css/layout.less @@ -4,33 +4,16 @@ html, body, #container { overflow: hidden; } -header, footer { - display: block; -} - -@headerheight: 153px; -@footerheight: 25px; - #container { - //Set padding on container so that #main can take 100% height - //If we don't do it, the scrollbars will be too large. - padding: @headerheight 0 @footerheight; -} + display: flex; + flex-direction: column; -header { - height: @headerheight; - //Substract #container padding - margin-top: -@headerheight; + > header, > footer, > .eventlog { + flex: 0 0 auto; + } } #main { - height: 100%; - display: block; - overflow-y: auto; -} - -footer { - //This starts at the beginning of the #container padding, all fine. - height: @footerheight; - line-height: @footerheight; -} + flex: 1 1 auto; + overflow: auto; +}
\ No newline at end of file diff --git a/web/src/index.html b/web/src/index.html index 509ef1eb..6cef0d25 100644 --- a/web/src/index.html +++ b/web/src/index.html @@ -12,7 +12,4 @@ <body> <div id="mitmproxy"></div> </body> -<script> - app = React.renderComponent(routes, document.body); -</script> </html>
\ No newline at end of file diff --git a/web/src/js/Connection.es6.js b/web/src/js/Connection.es6.js new file mode 100644 index 00000000..9daa82e2 --- /dev/null +++ b/web/src/js/Connection.es6.js @@ -0,0 +1,33 @@ +class Connection { + constructor(root){ + if(!root){ + root = location.origin + "/api/v1"; + } + this.root = root; + this.openWebSocketConnection(); + } + + openWebSocketConnection(){ + this.ws = new WebSocket(this.root.replace("http","ws") + "/ws"); + var ws = this.ws; + + ws.onopen = this.onopen.bind(this); + ws.onmessage = this.onmessage.bind(this); + ws.onerror = this.onerror.bind(this); + ws.onclose = this.onclose.bind(this); + } + + onopen(open){ + console.log("onopen", this, arguments); + } + onmessage(message){ + console.log("onmessage", this, arguments); + } + onerror(error){ + console.log("onerror", this, arguments); + } + onclose(close){ + console.log("onclose", this, arguments); + } + +} diff --git a/web/src/js/Dispatcher.es6.js b/web/src/js/Dispatcher.es6.js new file mode 100644 index 00000000..9bf70878 --- /dev/null +++ b/web/src/js/Dispatcher.es6.js @@ -0,0 +1,36 @@ +const PayloadSources = { + VIEW_ACTION: "VIEW_ACTION", + SERVER_ACTION: "SERVER_ACTION" +}; + +class Dispatcher { + + constructor() { + this.callbacks = []; + } + + register(callback){ + this.callbacks.push(callback); + } + + unregister(callback){ + var index = this.callbacks.indexOf(f); + if (index >= 0) { + this.callbacks.splice(this.callbacks.indexOf(f), 1); + } + } + + dispatch(payload){ + console.debug("dispatch", payload); + this.callbacks.forEach((callback) => { + callback(payload); + }); + } + +} + +AppDispatcher = new Dispatcher(); +AppDispatcher.dispatchViewAction = function(action){ + action.actionSource = PayloadSources.VIEW_ACTION; + this.dispatch(action); +};
\ No newline at end of file diff --git a/web/src/js/actions.es6.js b/web/src/js/actions.es6.js new file mode 100644 index 00000000..b6770074 --- /dev/null +++ b/web/src/js/actions.es6.js @@ -0,0 +1,14 @@ +var ActionTypes = { + SETTINGS_UPDATE: "SETTINGS_UPDATE", + LOG_ADD: "LOG_ADD" +}; + +var SettingsActions = { + update(settings) { + settings = _.merge({}, SettingsStore.getSettings(), settings); + AppDispatcher.dispatchViewAction({ + actionType: ActionTypes.SETTINGS_UPDATE, + settings: settings + }); + } +};
\ No newline at end of file diff --git a/web/src/js/app.js b/web/src/js/app.js new file mode 100644 index 00000000..2e4557af --- /dev/null +++ b/web/src/js/app.js @@ -0,0 +1,5 @@ +$(function(){ + + app = React.renderComponent(ProxyApp, document.body); + +});
\ No newline at end of file diff --git a/web/src/js/components/EventLog.react.js b/web/src/js/components/EventLog.react.js new file mode 100644 index 00000000..e710d30c --- /dev/null +++ b/web/src/js/components/EventLog.react.js @@ -0,0 +1,13 @@ +/** @jsx React.DOM */ + +var EventLog = React.createClass({ + render(){ + return ( + <div className="eventlog"> + <pre> + much log. + </pre> + </div> + ); + } +});
\ No newline at end of file diff --git a/web/src/js/footer.react.js b/web/src/js/components/Footer.react.js index 1b65e19d..ae0ccbe5 100644 --- a/web/src/js/footer.react.js +++ b/web/src/js/components/Footer.react.js @@ -1,7 +1,7 @@ /** @jsx React.DOM */ var Footer = React.createClass({ - render : function(){ + render(){ return ( <footer> <span className="label label-success">transparent mode</span> diff --git a/web/src/js/components/Header.react.js b/web/src/js/components/Header.react.js new file mode 100644 index 00000000..dc304d81 --- /dev/null +++ b/web/src/js/components/Header.react.js @@ -0,0 +1,88 @@ +/** @jsx React.DOM */ + +var MainMenu = React.createClass({ + mixins: [SettingsMixin], + handleSettingsChange() { + SettingsActions.update({ + showEventLog: this.refs.showEventLogInput.getDOMNode().checked + }); + }, + render(){ + return <div> + <label> + <input type="checkbox" ref="showEventLogInput" checked={this.state.settings.showEventLog} onChange={this.handleSettingsChange}/> + Show Event Log + </label> + </div>; + } +}); +var ToolsMenu = React.createClass({ + render(){ + return (<div>Tools Menu</div>); + } +}); +var ReportsMenu = React.createClass({ + render(){ + return (<div>Reports Menu</div>); + } +}); + + +var _Header_Entries = { + main: { + title: "Traffic", + route: "main", + menu: MainMenu + }, + tools: { + title: "Tools", + route: "main", + menu: ToolsMenu + }, + reports: { + title: "Visualization", + route: "reports", + menu: ReportsMenu + } +}; + +var Header = React.createClass({ + mixins: [SettingsMixin], + getInitialState(){ + return { + active: "main" + }; + }, + handleClick(active){ + this.setState({active: active}); + ReactRouter.transitionTo(_Header_Entries[active].route); + return false; + }, + handleFileClick(){ + console.log("File click"); + }, + + render(){ + var header = []; + for(var item in _Header_Entries){ + var classes = this.state.active == item ? "active" : ""; + header.push(<a key={item} href="#" className={classes} + onClick={this.handleClick.bind(this, item)}>{ _Header_Entries[item].title }</a>); + } + + var menu = _Header_Entries[this.state.active].menu(); + return ( + <header> + <div className="title-bar"> + mitmproxy { this.state.settings.version } + </div> + <nav> + <a href="#" className="special" onClick={this.handleFileClick}> File </a> + {header} + </nav> + <div className="menu"> + { menu } + </div> + </header>); + } +});
\ No newline at end of file diff --git a/web/src/js/components/ProxyApp.react.js b/web/src/js/components/ProxyApp.react.js new file mode 100644 index 00000000..7953d938 --- /dev/null +++ b/web/src/js/components/ProxyApp.react.js @@ -0,0 +1,35 @@ +/** @jsx React.DOM */ + +//TODO: Move out of here, just a stub. +var Reports = React.createClass({ + render(){ + return (<div>Report Editor</div>); + } +}); + + + +var ProxyAppMain = React.createClass({ + mixins: [SettingsMixin], + render() { + return ( + <div id="container"> + <Header/> + <div id="main"><this.props.activeRouteHandler/></div> + {this.state.settings.showEventLog ? <EventLog/> : null} + <Footer/> + </div> + ); + } +}); + + +var ProxyApp = ( + <ReactRouter.Routes location="hash"> + <ReactRouter.Route name="app" path="/" handler={ProxyAppMain}> + <ReactRouter.Route name="main" handler={TrafficTable}/> + <ReactRouter.Route name="reports" handler={Reports}/> + <ReactRouter.Redirect to="main"/> + </ReactRouter.Route> + </ReactRouter.Routes> +); diff --git a/web/src/js/components/TrafficTable.react.js b/web/src/js/components/TrafficTable.react.js new file mode 100644 index 00000000..442f8da2 --- /dev/null +++ b/web/src/js/components/TrafficTable.react.js @@ -0,0 +1,38 @@ +/** @jsx React.DOM */ + +var TrafficTable = React.createClass({ + /*getInitialState: function(){ + return { + flows: [] + }; + },*/ + componentDidMount: function () { + /*var flowStore = new DummyFlowStore([]); + this.setState({flowStore: flowStore}); + + flowStore.addChangeListener(this.onFlowsChange); + + $.getJSON("/flows.json").success(function (flows) { + flows.forEach(function (flow, i) { + window.setTimeout(function () { + flowStore.addFlow(flow); + }, _.random(i*400,i*400+1000)); + }); + }.bind(this));*/ + }, + componentWillUnmount: function(){ + //this.state.flowStore.close(); + }, + onFlowsChange: function(event, flows){ + //this.setState({flows: flows.getAll()}); + }, + render: function () { + /*var flows = this.state.flows.map(function(flow){ + return <div>{flow.request.method} {flow.request.scheme}://{flow.request.host}{flow.request.path}</div>; + }); *//**/ + x = "WTF"; + i = 12; + while(i--) x += x; + return <div><pre>{x}</pre></div>; + } +});
\ No newline at end of file diff --git a/web/src/js/datastructures.es6.js b/web/src/js/datastructures.es6.js deleted file mode 100644 index e9e2ee77..00000000 --- a/web/src/js/datastructures.es6.js +++ /dev/null @@ -1,105 +0,0 @@ -class EventEmitter { - constructor(){ - this.listeners = {}; - } - emit(event){ - if(!(event in this.listeners)){ - return; - } - this.listeners[event].forEach(function (listener) { - listener(event, this); - }.bind(this)); - } - addListener(event, f){ - this.listeners[event] = this.listeners[event] || []; - this.listeners[event].push(f); - } - removeListener(event, f){ - if(!(event in this.listeners)){ - return false; - } - var index = this.listeners.indexOf(f); - if (index >= 0) { - this.listeners.splice(this.listeners.indexOf(f), 1); - } - } -} - -var FLOW_CHANGED = "flow.changed"; - -class FlowStore extends EventEmitter{ - constructor() { - super(); - this.flows = []; - } - - getAll() { - return this.flows; - } - - close(){ - console.log("FlowStore.close()"); - this.listeners = []; - } - - emitChange() { - return this.emit(FLOW_CHANGED); - } - - addChangeListener(f) { - this.addListener(FLOW_CHANGED, f); - } - - removeChangeListener(f) { - this.removeListener(FLOW_CHANGED, f); - } -} - -class DummyFlowStore extends FlowStore { - constructor(flows) { - super(); - this.flows = flows; - } - - addFlow(flow) { - this.flows.push(flow); - this.emitChange(); - } -} - - -var SETTINGS_CHANGED = "settings.changed"; - -class Settings extends EventEmitter { - constructor(){ - super(); - this.settings = false; - } - - getAll(){ - return this.settings; - } - - emitChange() { - return this.emit(SETTINGS_CHANGED); - } - - addChangeListener(f) { - this.addListener(SETTINGS_CHANGED, f); - } - - removeChangeListener(f) { - this.removeListener(SETTINGS_CHANGED, f); - } -} - -class DummySettings extends Settings { - constructor(settings){ - super(); - this.settings = settings; - } - update(obj){ - _.merge(this.settings, obj); - this.emitChange(); - } -}
\ No newline at end of file diff --git a/web/src/js/header.react.js b/web/src/js/header.react.js deleted file mode 100644 index 85dc3106..00000000 --- a/web/src/js/header.react.js +++ /dev/null @@ -1,72 +0,0 @@ -/** @jsx React.DOM */ - -var MainMenu = React.createClass({ - render: function(){ - return (<div>Main Menu</div>); - } -}); -var ToolsMenu = React.createClass({ - render: function(){ - return (<div>Tools Menu</div>); - } -}); -var ReportsMenu = React.createClass({ - render: function(){ - return (<div>Reports Menu</div>); - } -}); - -var _Header_Entries = { - main: { - title: "Traffic", - route: "main", - menu: MainMenu - }, - tools: { - title: "Tools", - route: "main", - menu: ToolsMenu - }, - reports: { - title: "Visualization", - route: "reports", - menu: ReportsMenu - } -}; - -var Header = React.createClass({ - getInitialState: function(){ - return {active: "main"}; - }, - handleClick: function(active){ - this.setState({active: active}); - ReactRouter.transitionTo(_Header_Entries[active].route); - return false; - }, - handleFileClick: function(){ - console.log("File click"); - }, - render: function(){ - var header = []; - for(var item in _Header_Entries){ - var classes = this.state.active == item ? "active" : ""; - header.push(<a key={item} href="#" className={classes} - onClick={this.handleClick.bind(this, item)}>{ _Header_Entries[item].title }</a>); - } - - var menu = _Header_Entries[this.state.active].menu(); - return ( - <header> - <div className="title-bar"> - mitmproxy { this.props.settings.version } - </div> - <nav> - <a href="#" className="special" onClick={this.handleFileClick}> File </a> - {header} - </nav> - <div className="menu"> - { menu } - </div> - </header>); - } -});
\ No newline at end of file diff --git a/web/src/js/mitmproxy.react.js b/web/src/js/mitmproxy.react.js deleted file mode 100644 index 609d2014..00000000 --- a/web/src/js/mitmproxy.react.js +++ /dev/null @@ -1,82 +0,0 @@ -/** @jsx React.DOM */ - -var App = React.createClass({ - getInitialState: function () { - return { - settings: {} //TODO: How explicit should we get here? - //List all subattributes? - }; - }, - componentDidMount: function () { - //TODO: Replace DummyStore with real settings over WS (https://facebook.github.io/react/tips/initial-ajax.html) - var settingsStore = new DummySettings({ - version: "0.12" - }); - this.setState({settingsStore: settingsStore}); - settingsStore.addChangeListener(this.onSettingsChange); - }, - onSettingsChange: function(event, settings){ - this.setState({settings: settings.getAll()}); - }, - render: function () { - return ( - <div id="container"> - <Header settings={this.state.settings}/> - <div id="main"> - <this.props.activeRouteHandler settings={this.state.settings}/> - </div> - <Footer/> - </div> - ); - } -}); - -var TrafficTable = React.createClass({ - getInitialState: function(){ - return { - flows: [] - }; - }, - componentDidMount: function () { - var flowStore = new DummyFlowStore([]); - this.setState({flowStore: flowStore}); - - flowStore.addChangeListener(this.onFlowsChange); - - $.getJSON("/flows.json").success(function (flows) { - flows.forEach(function (flow, i) { - window.setTimeout(function () { - flowStore.addFlow(flow); - }, _.random(i*400,i*400+1000)); - }); - }.bind(this)); - }, - componentWillUnmount: function(){ - this.state.flowStore.close(); - }, - onFlowsChange: function(event, flows){ - this.setState({flows: flows.getAll()}); - }, - render: function () { - var flows = this.state.flows.map(function(flow){ - return <div>{flow.request.method} {flow.request.scheme}://{flow.request.host}{flow.request.path}</div>; - }); - return <pre>{flows}</pre>; - } -}); - -var Reports = React.createClass({ - render: function(){ - return (<div>Report Editor</div>); - } -}); - -var routes = ( - <ReactRouter.Routes location="hash"> - <ReactRouter.Route name="app" path="/" handler={App}> - <ReactRouter.Route name="main" handler={TrafficTable}/> - <ReactRouter.Route name="reports" handler={Reports}/> - <ReactRouter.Redirect to="main"/> - </ReactRouter.Route> - </ReactRouter.Routes> -);
\ No newline at end of file diff --git a/web/src/js/stores/EventLogStore.es6.js b/web/src/js/stores/EventLogStore.es6.js new file mode 100644 index 00000000..caa9d77d --- /dev/null +++ b/web/src/js/stores/EventLogStore.es6.js @@ -0,0 +1,42 @@ +class _EventLogStore extends EventEmitter { + constructor() { + /*jshint validthis: true */ + super(); + this.log = []; + } + getAll() { + return this.log; + } + handle(action) { + switch (action.actionType) { + case ActionTypes.LOG_ADD: + this.log.push(action.message); + this.emit("change"); + break; + default: + return; + } + } +} +var EventLogStore = new _EventLogStore(); +AppDispatcher.register(EventLogStore.handle.bind(EventLogStore)); + + +var EventLogMixin = { + getInitialState(){ + return { + log: EventLog.getAll() + }; + }, + componentDidMount(){ + SettingsStore.addListener("change", this._onEventLogChange); + }, + componentWillUnmount(){ + SettingsStore.removeListener("change", this._onEventLogChange); + }, + _onEventLogChange(){ + this.setState({ + log: EventLog.getAll() + }); + } +};
\ No newline at end of file diff --git a/web/src/js/stores/SettingsStore.es6.js b/web/src/js/stores/SettingsStore.es6.js new file mode 100644 index 00000000..7f3a6837 --- /dev/null +++ b/web/src/js/stores/SettingsStore.es6.js @@ -0,0 +1,42 @@ +class _SettingsStore extends EventEmitter { + constructor() { + /*jshint validthis: true */ + super(); + this.settings = { version: "0.12", showEventLog: true }; //FIXME: Need to get that from somewhere. + } + getSettings() { + return this.settings; + } + handle(action) { + switch (action.actionType) { + case ActionTypes.SETTINGS_UPDATE: + this.settings = action.settings; + this.emit("change"); + break; + default: + return; + } + } +} +var SettingsStore = new _SettingsStore(); +AppDispatcher.register(SettingsStore.handle.bind(SettingsStore)); + + +var SettingsMixin = { + getInitialState(){ + return { + settings: SettingsStore.getSettings() + }; + }, + componentDidMount(){ + SettingsStore.addListener("change", this._onSettingsChange); + }, + componentWillUnmount(){ + SettingsStore.removeListener("change", this._onSettingsChange); + }, + _onSettingsChange(){ + this.setState({ + settings: SettingsStore.getSettings() + }); + } +};
\ No newline at end of file diff --git a/web/src/js/stores/base.es6.js b/web/src/js/stores/base.es6.js new file mode 100644 index 00000000..9e9c69aa --- /dev/null +++ b/web/src/js/stores/base.es6.js @@ -0,0 +1,26 @@ +class EventEmitter { + constructor() { + this.listeners = {}; + } + emit(event) { + if (!(event in this.listeners)) { + return; + } + this.listeners[event].forEach(function(listener) { + listener(event, this); + }.bind(this)); + } + addListener(event, f) { + this.listeners[event] = this.listeners[event] || []; + this.listeners[event].push(f); + } + removeListener(event, f) { + if (!(event in this.listeners)) { + return false; + } + var index = this.listeners[event].indexOf(f); + if (index >= 0) { + this.listeners[event].splice(index, 1); + } + } +}
\ No newline at end of file |