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 | |
parent | 9f8d2eea64d4611c1e2f7e7043fe6d3ef9a6aa40 (diff) | |
download | mitmproxy-cbf18320cdbd05197f232da69b3c9a5391735156.tar.gz mitmproxy-cbf18320cdbd05197f232da69b3c9a5391735156.tar.bz2 mitmproxy-cbf18320cdbd05197f232da69b3c9a5391735156.zip |
client-side structure
27 files changed, 738 insertions, 507 deletions
diff --git a/libmproxy/web/static/css/app.css b/libmproxy/web/static/css/app.css index 8ef7842a..fe8fd79a 100644 --- a/libmproxy/web/static/css/app.css +++ b/libmproxy/web/static/css/app.css @@ -13,25 +13,18 @@ body, margin: 0; overflow: hidden; } -header, -footer { - display: block; -} #container { - padding: 153px 0 25px; + display: flex; + flex-direction: column; } -header { - height: 153px; - margin-top: -153px; +#container > header, +#container > footer, +#container > .eventlog { + flex: 0 0 auto; } #main { - height: 100%; - display: block; - overflow-y: auto; -} -footer { - height: 25px; - line-height: 25px; + flex: 1 1 auto; + overflow: auto; } header { background-color: white; @@ -55,11 +48,11 @@ header nav a.active { } header nav a:hover { /* - @preview: lightgrey; - border-top-color: @preview; - border-left-color: @preview; - border-right-color: @preview; - */ + @preview: lightgrey; + border-top-color: @preview; + border-left-color: @preview; + border-right-color: @preview; + */ text-decoration: none; } header nav a.special { @@ -70,18 +63,22 @@ header nav a.special { header nav a.special:hover { background-color: #5386c6; } -header nav:before { - content: " "; -} -header nav:after { - clear: both; -} header .menu { height: 100px; border-bottom: solid #a6a6a6 1px; } +.eventlog { + flex: 0 0 auto; +} +.eventlog pre { + margin: 0; + border-radius: 0; + height: 200px; + overflow: auto; +} footer { - padding: 0 10px; + box-shadow: 0 -1px 3px #d3d3d3; + padding: 0px 10px 3px; } /*# sourceMappingURL=../css/app.css.map */
\ No newline at end of file diff --git a/libmproxy/web/static/fonts/FontAwesome.otf b/libmproxy/web/static/fonts/FontAwesome.otf Binary files differdeleted file mode 100644 index 81c9ad94..00000000 --- a/libmproxy/web/static/fonts/FontAwesome.otf +++ /dev/null diff --git a/libmproxy/web/static/index.html b/libmproxy/web/static/index.html deleted file mode 100644 index 509ef1eb..00000000 --- a/libmproxy/web/static/index.html +++ /dev/null @@ -1,18 +0,0 @@ -<!DOCTYPE html> -<html> -<head lang="en"> - <meta charset="UTF-8"> - <title>mitmproxy</title> - <meta name="viewport" content="width=device-width, initial-scale=1"> - <link rel="stylesheet" href="css/vendor.css"/> - <link rel="stylesheet" href="css/app.css"/> - <script src="js/vendor.js"></script> - <script src="js/app.js"></script> -</head> -<body> -<div id="mitmproxy"></div> -</body> -<script> - app = React.renderComponent(routes, document.body); -</script> -</html>
\ No newline at end of file diff --git a/libmproxy/web/static/js/app.js b/libmproxy/web/static/js/app.js index eeb6e5dd..7873046a 100644 --- a/libmproxy/web/static/js/app.js +++ b/libmproxy/web/static/js/app.js @@ -1,135 +1,261 @@ +const PayloadSources = { + VIEW_ACTION: "VIEW_ACTION", + SERVER_ACTION: "SERVER_ACTION" +}; + - function EventEmitter(){"use strict"; - this.listeners = {}; - } - EventEmitter.prototype.emit=function(event){"use strict"; - if(!(event in this.listeners)){ - return; - } - this.listeners[event].forEach(function (listener) { - listener(event, this); - }.bind(this)); - }; - EventEmitter.prototype.addListener=function(event, f){"use strict"; - this.listeners[event] = this.listeners[event] || []; - this.listeners[event].push(f); - }; - EventEmitter.prototype.removeListener=function(event, f){"use strict"; - if(!(event in this.listeners)){ - return false; - } - var index = this.listeners.indexOf(f); - if (index >= 0) { - this.listeners.splice(this.listeners.indexOf(f), 1); - } - }; + function Dispatcher() {"use strict"; + this.callbacks = []; + } -var FLOW_CHANGED = "flow.changed"; + Dispatcher.prototype.register=function(callback){"use strict"; + this.callbacks.push(callback); + }; -for(var EventEmitter____Key in EventEmitter){if(EventEmitter.hasOwnProperty(EventEmitter____Key)){FlowStore[EventEmitter____Key]=EventEmitter[EventEmitter____Key];}}var ____SuperProtoOfEventEmitter=EventEmitter===null?null:EventEmitter.prototype;FlowStore.prototype=Object.create(____SuperProtoOfEventEmitter);FlowStore.prototype.constructor=FlowStore;FlowStore.__superConstructor__=EventEmitter; - function FlowStore() {"use strict"; - EventEmitter.call(this); - this.flows = []; + Dispatcher.prototype.unregister=function(callback){"use strict"; + var index = this.callbacks.indexOf(f); + if (index >= 0) { + this.callbacks.splice(this.callbacks.indexOf(f), 1); } + }; - FlowStore.prototype.getAll=function() {"use strict"; - return this.flows; - }; + Dispatcher.prototype.dispatch=function(payload){"use strict"; + console.debug("dispatch", payload); + this.callbacks.forEach(function(callback) { + callback(payload); + }); + }; - FlowStore.prototype.close=function(){"use strict"; - console.log("FlowStore.close()"); - this.listeners = []; - }; - FlowStore.prototype.emitChange=function() {"use strict"; - return this.emit(FLOW_CHANGED); - }; - FlowStore.prototype.addChangeListener=function(f) {"use strict"; - this.addListener(FLOW_CHANGED, f); - }; +AppDispatcher = new Dispatcher(); +AppDispatcher.dispatchViewAction = function(action){ + action.actionSource = PayloadSources.VIEW_ACTION; + this.dispatch(action); +}; +var ActionTypes = { + SETTINGS_UPDATE: "SETTINGS_UPDATE", + LOG_ADD: "LOG_ADD" +}; - FlowStore.prototype.removeChangeListener=function(f) {"use strict"; - this.removeListener(FLOW_CHANGED, f); - }; +var SettingsActions = { + update:function(settings) { + settings = _.merge({}, SettingsStore.getSettings(), settings); + AppDispatcher.dispatchViewAction({ + actionType: ActionTypes.SETTINGS_UPDATE, + settings: settings + }); + } +}; + function EventEmitter() {"use strict"; + this.listeners = {}; + } + EventEmitter.prototype.emit=function(event) {"use strict"; + if (!(event in this.listeners)) { + return; + } + this.listeners[event].forEach(function(listener) { + listener(event, this); + }.bind(this)); + }; + EventEmitter.prototype.addListener=function(event, f) {"use strict"; + this.listeners[event] = this.listeners[event] || []; + this.listeners[event].push(f); + }; + EventEmitter.prototype.removeListener=function(event, f) {"use strict"; + if (!(event in this.listeners)) { + return false; + } + var index = this.listeners[event].indexOf(f); + if (index >= 0) { + this.listeners[event].splice(index, 1); + } + }; + +for(var EventEmitter____Key in EventEmitter){if(EventEmitter.hasOwnProperty(EventEmitter____Key)){_SettingsStore[EventEmitter____Key]=EventEmitter[EventEmitter____Key];}}var ____SuperProtoOfEventEmitter=EventEmitter===null?null:EventEmitter.prototype;_SettingsStore.prototype=Object.create(____SuperProtoOfEventEmitter);_SettingsStore.prototype.constructor=_SettingsStore;_SettingsStore.__superConstructor__=EventEmitter; + function _SettingsStore() {"use strict"; + /*jshint validthis: true */ + EventEmitter.call(this); + this.settings = { version: "0.12", showEventLog: true }; //FIXME: Need to get that from somewhere. + } + _SettingsStore.prototype.getSettings=function() {"use strict"; + return this.settings; + }; + _SettingsStore.prototype.handle=function(action) {"use strict"; + 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:function(){ + return { + settings: SettingsStore.getSettings() + }; + }, + componentDidMount:function(){ + SettingsStore.addListener("change", this._onSettingsChange); + }, + componentWillUnmount:function(){ + SettingsStore.removeListener("change", this._onSettingsChange); + }, + _onSettingsChange:function(){ + this.setState({ + settings: SettingsStore.getSettings() + }); + } +}; +for(var EventEmitter____Key in EventEmitter){if(EventEmitter.hasOwnProperty(EventEmitter____Key)){_EventLogStore[EventEmitter____Key]=EventEmitter[EventEmitter____Key];}}var ____SuperProtoOfEventEmitter=EventEmitter===null?null:EventEmitter.prototype;_EventLogStore.prototype=Object.create(____SuperProtoOfEventEmitter);_EventLogStore.prototype.constructor=_EventLogStore;_EventLogStore.__superConstructor__=EventEmitter; + function _EventLogStore() {"use strict"; + /*jshint validthis: true */ + EventEmitter.call(this); + this.log = []; + } + _EventLogStore.prototype.getAll=function() {"use strict"; + return this.log; + }; + _EventLogStore.prototype.handle=function(action) {"use strict"; + 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:function(){ + return { + log: EventLog.getAll() + }; + }, + componentDidMount:function(){ + SettingsStore.addListener("change", this._onEventLogChange); + }, + componentWillUnmount:function(){ + SettingsStore.removeListener("change", this._onEventLogChange); + }, + _onEventLogChange:function(){ + this.setState({ + log: EventLog.getAll() + }); + } +}; -for(var FlowStore____Key in FlowStore){if(FlowStore.hasOwnProperty(FlowStore____Key)){DummyFlowStore[FlowStore____Key]=FlowStore[FlowStore____Key];}}var ____SuperProtoOfFlowStore=FlowStore===null?null:FlowStore.prototype;DummyFlowStore.prototype=Object.create(____SuperProtoOfFlowStore);DummyFlowStore.prototype.constructor=DummyFlowStore;DummyFlowStore.__superConstructor__=FlowStore; - function DummyFlowStore(flows) {"use strict"; - FlowStore.call(this); - this.flows = flows; + function Connection(root){"use strict"; + if(!root){ + root = location.origin + "/api/v1"; + } + this.root = root; + this.openWebSocketConnection(); } - DummyFlowStore.prototype.addFlow=function(flow) {"use strict"; - this.flows.push(flow); - this.emitChange(); + Connection.prototype.openWebSocketConnection=function(){"use strict"; + 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); + }; + + Connection.prototype.onopen=function(open){"use strict"; + console.log("onopen", this, arguments); + }; + Connection.prototype.onmessage=function(message){"use strict"; + console.log("onmessage", this, arguments); + }; + Connection.prototype.onerror=function(error){"use strict"; + console.log("onerror", this, arguments); + }; + Connection.prototype.onclose=function(close){"use strict"; + console.log("onclose", this, arguments); }; -var SETTINGS_CHANGED = "settings.changed"; -for(EventEmitter____Key in EventEmitter){if(EventEmitter.hasOwnProperty(EventEmitter____Key)){Settings[EventEmitter____Key]=EventEmitter[EventEmitter____Key];}}Settings.prototype=Object.create(____SuperProtoOfEventEmitter);Settings.prototype.constructor=Settings;Settings.__superConstructor__=EventEmitter; - function Settings(){"use strict"; - EventEmitter.call(this); - this.settings = false; + function Connection(root){"use strict"; + if(!root){ + root = location.origin + "/api/v1"; + } + this.root = root; + this.openWebSocketConnection(); } - Settings.prototype.getAll=function(){"use strict"; - return this.settings; - }; + Connection.prototype.openWebSocketConnection=function(){"use strict"; + this.ws = new WebSocket(this.root.replace("http","ws") + "/ws"); + var ws = this.ws; - Settings.prototype.emitChange=function() {"use strict"; - return this.emit(SETTINGS_CHANGED); + ws.onopen = this.onopen.bind(this); + ws.onmessage = this.onmessage.bind(this); + ws.onerror = this.onerror.bind(this); + ws.onclose = this.onclose.bind(this); }; - Settings.prototype.addChangeListener=function(f) {"use strict"; - this.addListener(SETTINGS_CHANGED, f); + Connection.prototype.onopen=function(open){"use strict"; + console.log("onopen", this, arguments); }; - - Settings.prototype.removeChangeListener=function(f) {"use strict"; - this.removeListener(SETTINGS_CHANGED, f); + Connection.prototype.onmessage=function(message){"use strict"; + console.log("onmessage", this, arguments); }; - - -for(var Settings____Key in Settings){if(Settings.hasOwnProperty(Settings____Key)){DummySettings[Settings____Key]=Settings[Settings____Key];}}var ____SuperProtoOfSettings=Settings===null?null:Settings.prototype;DummySettings.prototype=Object.create(____SuperProtoOfSettings);DummySettings.prototype.constructor=DummySettings;DummySettings.__superConstructor__=Settings; - function DummySettings(settings){"use strict"; - Settings.call(this); - this.settings = settings; - } - DummySettings.prototype.update=function(obj){"use strict"; - _.merge(this.settings, obj); - this.emitChange(); + Connection.prototype.onerror=function(error){"use strict"; + console.log("onerror", this, arguments); + }; + Connection.prototype.onclose=function(close){"use strict"; + console.log("onclose", this, arguments); }; -/** @jsx React.DOM */ -var Footer = React.createClass({displayName: 'Footer', - render : function(){ - return (React.DOM.footer(null, - React.DOM.span({className: "label label-success"}, "transparent mode") - )); - } -}); + /** @jsx React.DOM */ var MainMenu = React.createClass({displayName: 'MainMenu', - render : function(){ - return (React.DOM.div(null, "Main Menu")); + mixins: [SettingsMixin], + handleSettingsChange:function() { + SettingsActions.update({ + showEventLog: this.refs.showEventLogInput.getDOMNode().checked + }); + }, + render:function(){ + return React.DOM.div(null, + React.DOM.label(null, + React.DOM.input({type: "checkbox", ref: "showEventLogInput", checked: this.state.settings.showEventLog, onChange: this.handleSettingsChange}), + "Show Event Log" + ) + ); } }); var ToolsMenu = React.createClass({displayName: 'ToolsMenu', - render : function(){ + render:function(){ return (React.DOM.div(null, "Tools Menu")); } }); var ReportsMenu = React.createClass({displayName: 'ReportsMenu', - render : function(){ + render:function(){ return (React.DOM.div(null, "Reports Menu")); } }); + var _Header_Entries = { main: { title: "Traffic", @@ -149,83 +275,55 @@ var _Header_Entries = { }; var Header = React.createClass({displayName: 'Header', - getInitialState: function(){ - return {active: "main"}; + mixins: [SettingsMixin], + getInitialState:function(){ + return { + active: "main" + }; }, - handleClick: function(active){ + handleClick:function(active){ this.setState({active: active}); ReactRouter.transitionTo(_Header_Entries[active].route); return false; }, - handleFileClick: function(){ + handleFileClick:function(){ console.log("File click"); }, - render: function(){ + render:function(){ var header = []; for(var item in _Header_Entries){ var classes = this.state.active == item ? "active" : ""; header.push(React.DOM.a({key: item, href: "#", className: classes, - onClick: this.handleClick.bind(this, item)}, _Header_Entries[item].title)); + onClick: this.handleClick.bind(this, item)}, _Header_Entries[item].title)); } var menu = _Header_Entries[this.state.active].menu(); return ( - React.DOM.header(null, - React.DOM.div({className: "title-bar"}, - "mitmproxy ", this.props.settings.version - ), - React.DOM.nav(null, - React.DOM.a({href: "#", className: "special", onClick: this.handleFileClick}, " File "), - header - ), - React.DOM.div({className: "menu"}, - menu - ) - )); + React.DOM.header(null, + React.DOM.div({className: "title-bar"}, + "mitmproxy ", this.state.settings.version + ), + React.DOM.nav(null, + React.DOM.a({href: "#", className: "special", onClick: this.handleFileClick}, " File "), + header + ), + React.DOM.div({className: "menu"}, + menu + ) + )); } }); /** @jsx React.DOM */ -var App = React.createClass({displayName: 'App', - 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 ( - React.DOM.div({id: "container"}, - Header({settings: this.state.settings}), - React.DOM.div({id: "main"}, - this.props.activeRouteHandler({settings: this.state.settings}) - ), - Footer(null) - ) - ); - } -}); - var TrafficTable = React.createClass({displayName: 'TrafficTable', - getInitialState: function(){ + /*getInitialState: function(){ return { flows: [] }; - }, + },*/ componentDidMount: function () { - var flowStore = new DummyFlowStore([]); + /*var flowStore = new DummyFlowStore([]); this.setState({flowStore: flowStore}); flowStore.addChangeListener(this.onFlowsChange); @@ -236,35 +334,87 @@ var TrafficTable = React.createClass({displayName: 'TrafficTable', flowStore.addFlow(flow); }, _.random(i*400,i*400+1000)); }); - }.bind(this)); + }.bind(this));*/ }, componentWillUnmount: function(){ - this.state.flowStore.close(); + //this.state.flowStore.close(); }, onFlowsChange: function(event, flows){ - this.setState({flows: flows.getAll()}); + //this.setState({flows: flows.getAll()}); }, render: function () { - var flows = this.state.flows.map(function(flow){ - return React.DOM.div(null, flow.request.method, " ", flow.request.scheme, "://", flow.request.host, flow.request.path); - }); - return React.DOM.pre(null, flows); + /*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 React.DOM.div(null, React.DOM.pre(null, x)); } }); +/** @jsx React.DOM */ +var EventLog = React.createClass({displayName: 'EventLog', + render:function(){ + return ( + React.DOM.div({className: "eventlog"}, + React.DOM.pre(null, + "much log." + ) + ) + ); + } +}); +/** @jsx React.DOM */ + +var Footer = React.createClass({displayName: 'Footer', + render:function(){ + return ( + React.DOM.footer(null, + React.DOM.span({className: "label label-success"}, "transparent mode") + ) + ); + } +}); +/** @jsx React.DOM */ + +//TODO: Move out of here, just a stub. var Reports = React.createClass({displayName: 'Reports', - render: function(){ + render:function(){ return (React.DOM.div(null, "Report Editor")); } }); -var routes = ( + + +var ProxyAppMain = React.createClass({displayName: 'ProxyAppMain', + mixins: [SettingsMixin], + render:function() { + return ( + React.DOM.div({id: "container"}, + Header(null), + React.DOM.div({id: "main"}, this.props.activeRouteHandler(null)), + this.state.settings.showEventLog ? EventLog(null) : null, + Footer(null) + ) + ); + } +}); + + +var ProxyApp = ( ReactRouter.Routes({location: "hash"}, - ReactRouter.Route({name: "app", path: "/", handler: App}, + ReactRouter.Route({name: "app", path: "/", handler: ProxyAppMain}, ReactRouter.Route({name: "main", handler: TrafficTable}), ReactRouter.Route({name: "reports", handler: Reports}), ReactRouter.Redirect({to: "main"}) ) ) ); + +$(function(){ + + app = React.renderComponent(ProxyApp, document.body); + +}); //# sourceMappingURL=app.js.map
\ No newline at end of file diff --git a/libmproxy/web/templates/index.html b/libmproxy/web/templates/index.html index 5907dacd..6cef0d25 100644 --- a/libmproxy/web/templates/index.html +++ b/libmproxy/web/templates/index.html @@ -4,15 +4,12 @@ <meta charset="UTF-8"> <title>mitmproxy</title> <meta name="viewport" content="width=device-width, initial-scale=1"> - <link rel="stylesheet" href="static/css/vendor.css"/> - <link rel="stylesheet" href="static/css/app.css"/> - <script src="static/js/vendor.js"></script> - <script src="static/js/app.js"></script> + <link rel="stylesheet" href="css/vendor.css"/> + <link rel="stylesheet" href="css/app.css"/> + <script src="js/vendor.js"></script> + <script src="js/app.js"></script> </head> <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/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 |